first commit

This commit is contained in:
2024-01-11 00:49:37 +08:00
commit 1d4ca069e7
169 changed files with 44956 additions and 0 deletions

9
src/App.test.tsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

136
src/App.tsx Normal file
View File

@@ -0,0 +1,136 @@
import React, { useState, useEffect } from 'react';
import { Layout, notification } from 'antd';
import umbrella from 'umbrella-storage';
import { useAlita } from 'redux-alita';
import Routes from './routes';
import SiderCustom from './components/SiderCustom';
import HeaderCustom from './components/HeaderCustom';
import { ThemePicker, Copyright } from './components/widget';
import { checkLogin } from './utils';
import { fetchMenu } from './service';
import classNames from 'classnames';
import { SmileOutlined } from '@ant-design/icons';
const { Content, Footer } = Layout;
type AppProps = {};
function checkIsMobile() {
const clientWidth = window.innerWidth;
return clientWidth <= 992;
}
let _resizeThrottled = false;
function resizeListener(handler: (isMobile: boolean) => void) {
const delay = 250;
if (!_resizeThrottled) {
_resizeThrottled = true;
const timer = setTimeout(() => {
handler(checkIsMobile());
_resizeThrottled = false;
clearTimeout(timer);
}, delay);
}
}
function handleResize(handler: (isMobile: boolean) => void) {
window.addEventListener('resize', resizeListener.bind(null, handler));
}
function openFNotification() {
const openNotification = () => {
notification.open({
message: '博主-yezihaohao',
description: (
<div>
<p>
GitHub地址
<a
href="https://github.com/yezihaohao"
target="_blank"
rel="noopener noreferrer"
>
https://github.com/yezihaohao
</a>
</p>
<p>
<a
href="https://yezihaohao.github.io/"
target="_blank"
rel="noopener noreferrer"
>
https://yezihaohao.github.io/
</a>
</p>
</div>
),
icon: <SmileOutlined style={{ color: 'red' }} />,
duration: 0,
});
umbrella.setLocalStorage('hideBlog', true);
};
const storageFirst = umbrella.getLocalStorage('hideBlog');
if (!storageFirst) {
openNotification();
}
}
/**
* 获取服务端异步菜单
* @param handler 执行回调
*/
function fetchSmenu(handler: any) {
const setAlitaMenu = (menus: any) => {
handler(menus);
// this.props.setAlitaState({ stateName: 'smenus', data: menus });
};
setAlitaMenu(umbrella.getLocalStorage('smenus') || []);
fetchMenu().then((smenus) => {
setAlitaMenu(smenus);
umbrella.setLocalStorage('smenus', smenus);
});
}
const App = (props: AppProps) => {
const [collapsed, setCollapsed] = useState<boolean>(false);
const [auth, responsive, setAlita] = useAlita(
{ auth: { permissions: null } },
{ responsive: { isMobile: false } },
{ light: true }
);
useEffect(() => {
let user = umbrella.getLocalStorage('user');
user && setAlita('auth', user);
setAlita('responsive', { isMobile: checkIsMobile() });
handleResize((isMobile: boolean) => setAlita('responsive', { isMobile }));
openFNotification();
fetchSmenu((smenus: any[]) => setAlita('smenus', smenus));
}, [setAlita]);
function toggle() {
setCollapsed(!collapsed);
}
return (
<Layout>
{!responsive.isMobile && checkLogin(auth.permissions) && (
<SiderCustom collapsed={collapsed} />
)}
<ThemePicker />
<Layout
className={classNames('app_layout', { 'app_layout-mobile': responsive.isMobile })}
>
<HeaderCustom toggle={toggle} collapsed={collapsed} user={auth || {}} />
<Content className="app_layout_content">
<Routes auth={auth} />
</Content>
<Footer className="app_layout_foot">
<Copyright />
</Footer>
</Layout>
</Layout>
);
};
export default App;

17
src/Page.tsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import NotFound from './components/pages/NotFound';
import Login from './components/pages/Login';
import App from './App';
export default () => (
<Router>
<Switch>
<Route exact path="/" render={() => <Redirect to="/app/dashboard/index" push />} />
<Route path="/app" component={App} />
<Route path="/404" component={NotFound} />
<Route path="/login" component={Login} />
<Route component={NotFound} />
</Switch>
</Router>
);

View File

@@ -0,0 +1,137 @@
/**
* Created by hao.cheng on 2017/4/13.
*/
import React, { useEffect, useState } from 'react';
import screenfull from 'screenfull';
import avater from '../style/imgs/b1.jpg';
import SiderCustom from './SiderCustom';
import { Menu, Layout, Badge, Popover } from 'antd';
import { gitOauthToken, gitOauthInfo } from '../service';
import { parseQuery } from '../utils';
import { useHistory } from 'react-router-dom';
import { PwaInstaller } from './widget';
import { useAlita } from 'redux-alita';
import umbrella from 'umbrella-storage';
import { useSwitch } from '../utils/hooks';
import {
ArrowsAltOutlined,
BarsOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
NotificationOutlined,
} from '@ant-design/icons';
const { Header } = Layout;
const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;
type HeaderCustomProps = {
toggle: () => void;
collapsed: boolean;
user: any;
responsive?: any;
path?: string;
};
const HeaderCustom = (props: HeaderCustomProps) => {
const [user, setUser] = useState<any>();
const [responsive] = useAlita('responsive', { light: true });
const [visible, turn] = useSwitch();
const history = useHistory();
useEffect(() => {
const query = parseQuery();
let storageUser = umbrella.getLocalStorage('user');
if (!storageUser && query.code) {
gitOauthToken(query.code as string).then((res: any) => {
gitOauthInfo(res.access_token).then((info: any) => {
setUser({
user: info,
});
umbrella.setLocalStorage('user', info);
});
});
} else {
setUser({
user: storageUser,
});
}
}, []);
const screenFull = () => {
if (screenfull.isEnabled) {
screenfull.toggle();
}
};
const menuClick = (e: any) => {
e.key === 'logout' && logout();
};
const logout = () => {
umbrella.removeLocalStorage('user');
history.push('/login');
};
return (
<Header className="custom-theme header">
{responsive?.isMobile ? (
<Popover
content={<SiderCustom popoverHide={turn.turnOff} />}
trigger="click"
placement="bottomLeft"
visible={visible}
onVisibleChange={(visible) => (visible ? turn.turnOn() : turn.turnOff())}
>
<BarsOutlined className="header__trigger custom-trigger" />
</Popover>
) : props.collapsed ? (
<MenuUnfoldOutlined
className="header__trigger custom-trigger"
onClick={props.toggle}
/>
) : (
<MenuFoldOutlined
className="header__trigger custom-trigger"
onClick={props.toggle}
/>
)}
<Menu
mode="horizontal"
style={{ lineHeight: '64px', float: 'right' }}
onClick={menuClick}
>
<Menu.Item key="pwa">
<PwaInstaller />
</Menu.Item>
<Menu.Item key="full">
<ArrowsAltOutlined onClick={screenFull} />
</Menu.Item>
<Menu.Item key="1">
<Badge count={25} overflowCount={10} style={{ marginLeft: 10 }}>
<NotificationOutlined />
</Badge>
</Menu.Item>
<SubMenu
title={
<span className="avatar">
<img src={avater} alt="头像" />
<i className="on bottom b-white" />
</span>
}
>
<MenuItemGroup title="用户中心">
<Menu.Item key="setting:1"> - {user?.userName}</Menu.Item>
<Menu.Item key="setting:2"></Menu.Item>
<Menu.Item key="logout">
<span onClick={logout}>退</span>
</Menu.Item>
</MenuItemGroup>
<MenuItemGroup title="设置中心">
<Menu.Item key="setting:3"></Menu.Item>
<Menu.Item key="setting:4"></Menu.Item>
</MenuItemGroup>
</SubMenu>
</Menu>
</Header>
);
};
export default HeaderCustom;

14
src/components/Page.tsx Normal file
View File

@@ -0,0 +1,14 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React, { ReactNode } from 'react';
interface PageProps {
children?: ReactNode;
}
const Page = (props: PageProps) => {
return <div style={{ height: '100%' }}>{props.children}</div>;
};
export default Page;

View File

@@ -0,0 +1,103 @@
/**
* Created by hao.cheng on 2017/4/13.
*/
import React, { useState, useEffect } from 'react';
import { Layout } from 'antd';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import routes from '../routes/config';
import SiderMenu from './SiderMenu';
import { useAlita } from 'redux-alita';
import { useSwitch } from '../utils/hooks';
import { usePrevious } from 'ahooks';
const { Sider } = Layout;
type SiderCustomProps = RouteComponentProps<any> & {
popoverHide?: () => void;
collapsed?: boolean;
smenus?: any;
};
interface IMenu {
openKeys: string[];
selectedKey: string;
}
const SiderCustom = (props: SiderCustomProps) => {
const [collapsed, tCollapsed] = useSwitch();
const [firstHide, tFirstHide] = useSwitch();
const [menu, setMenu] = useState<IMenu>({ openKeys: [''], selectedKey: '' });
// 异步菜单
const [smenus] = useAlita({ smenus: [] }, { light: true });
const { location, collapsed: pCollapsed } = props;
const prePathname = usePrevious(props.location.pathname);
useEffect(() => {
const recombineOpenKeys = (openKeys: string[]) => {
let i = 0;
let strPlus = '';
let tempKeys: string[] = [];
// 多级菜单循环处理
while (i < openKeys.length) {
strPlus += openKeys[i];
tempKeys.push(strPlus);
i++;
}
return tempKeys;
};
const getOpenAndSelectKeys = () => {
return {
openKeys: recombineOpenKeys(location.pathname.match(/[/](\w+)/gi) || []),
selectedKey: location.pathname,
};
};
if (pCollapsed !== collapsed) {
setMenu(getOpenAndSelectKeys());
tCollapsed.setSwitcher(!!pCollapsed);
tFirstHide.setSwitcher(!!pCollapsed);
}
if (prePathname !== location.pathname) {
setMenu(getOpenAndSelectKeys());
}
}, [prePathname, location.pathname, collapsed, tFirstHide, tCollapsed, pCollapsed]);
const menuClick = (e: any) => {
setMenu((state) => ({ ...state, selectedKey: e.key }));
props.popoverHide?.(); // 响应式布局控制小屏幕点击菜单时隐藏菜单操作
};
const openMenu: any = (v: string[]) => {
setMenu((state) => ({ ...state, openKeys: v }));
tFirstHide.turnOff();
};
return (
<Sider
trigger={null}
breakpoint="lg"
collapsed={collapsed}
style={{ overflowY: 'auto' }}
className="sider-custom"
>
<div className="logo" />
<SiderMenu
menus={[...routes.menus, ...smenus]}
onClick={menuClick}
mode="inline"
selectedKeys={[menu.selectedKey]}
openKeys={firstHide ? [] : menu.openKeys}
onOpenChange={openMenu}
/>
<style>
{`
#nprogress .spinner{
left: ${collapsed ? '70px' : '206px'};
right: 0 !important;
}
`}
</style>
</Sider>
);
};
export default withRouter(SiderCustom);

View File

@@ -0,0 +1,99 @@
import React, { useState, useEffect } from 'react';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import { IFMenu } from '../routes/config';
import { MenuProps } from 'antd/lib/menu';
const renderMenuItem = (
item: IFMenu // item.route 菜单单独跳转的路由
) => (
<Menu.Item key={item.key}>
<Link to={(item.route || item.key) + (item.query || '')}>
{/* {item.icon && <Icon type={item.icon} />} */}
<span className="nav-text">{item.title}</span>
</Link>
</Menu.Item>
);
const renderSubMenu = (item: IFMenu) => {
return (
<Menu.SubMenu
key={item.key}
title={
<span>
{/* {item.icon && <Icon type={item.icon} />} */}
<span className="nav-text">{item.title}</span>
</span>
}
>
{item.subs!.map((sub) => (sub.subs ? renderSubMenu(sub) : renderMenuItem(sub)))}
</Menu.SubMenu>
);
};
type SiderMenuProps = MenuProps & {
menus: any;
onClick: (e: any) => void;
selectedKeys: string[];
openKeys?: string[];
onOpenChange: (v: string[]) => void;
};
const SiderMenu = ({ menus, ...props }: SiderMenuProps) => {
const [dragItems, setDragItems] = useState<any>([]);
useEffect(() => {
setDragItems(menus);
}, [menus]);
const reorder = (list: any, startIndex: number, endIndex: number) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const onDragEnd = (result: any) => {
// dropped outside the list
if (!result.destination) {
return;
}
const _items = reorder(dragItems, result.source.index, result.destination.index);
setDragItems(_items);
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{dragItems.map((item: IFMenu, index: number) => (
<Draggable key={item.key} draggableId={item.key} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onDragStart={(e: React.DragEvent<any>) =>
provided.dragHandleProps &&
provided.dragHandleProps.onDragStart(e as any)
}
>
<Menu {...props}>
{item.subs!
? renderSubMenu(item)
: renderMenuItem(item)}
</Menu>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
export default React.memo(SiderMenu);

View File

@@ -0,0 +1,137 @@
/**
* Created by hao.cheng on 2017/5/8.
*/
import React from 'react';
import { Row, Col, Card, Switch } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
class BasicAnimations extends React.Component {
state = {
animated: false,
animatedOne: -1,
};
animatedAll = (checked: boolean) => {
checked && this.setState({ animated: true });
!checked && this.setState({ animated: false });
};
animatedOne = (i: number) => {
this.setState({ animatedOne: i });
};
animatedOneOver = () => {
this.setState({ animatedOne: -1 });
};
render() {
const animations = [
'bounce',
'flash',
'rubberBand',
'shake',
'headShake',
'swing',
'tada',
'wobble',
'jello',
'bounceIn',
'bounceInDown',
'bounceInLeft',
'bounceInRight',
'bounceOut',
'bounceOutDown',
'bounceOutLeft',
'bounceOutLeft',
'bounceOutUp',
'fadeIn',
'fadeInDown',
'fadeInDownBig',
'fadeInLeft',
'fadeInLeftBig',
'fadeInRight',
'fadeInRightBig',
'fadeInUp',
'fadeInUpBig',
'fadeOut',
'fadeOutDown',
'fadeOutDownBig',
'fadeOutLeft',
'fadeOutLeftBig',
'fadeOutRight',
'fadeOutRightBig',
'fadeOutUp',
'fadeOutUpBig',
'flipInX',
'flipInY',
'flipOutX',
'flipOutY',
'lightSpeedIn',
'lightSpeedOut',
'rotateIn',
'rotateInDownLeft',
'rotateInDownRight',
'rotateInUpLeft',
'rotateInUpRight',
'rotateOut',
'rotateOutDownLeft',
'rotateOutDownRight',
'rotateOutUpLeft',
'rotateOutUpRight',
'hinge',
'jackInTheBox',
'rollIn',
'rollOut',
'zoomIn',
'zoomInDown',
'zoomInLeft',
'zoomInRight',
'zoomInUp',
'zoomOut',
'zoomOutDown',
'zoomOutLeft',
'zoomOutRight',
'zoomOutUp',
'slideInDown',
'slideInLeft',
'slideInRight',
'slideInUp',
'slideOutDown',
'slideOutLeft',
'slideOutRight',
'slideOutUp',
];
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['动画', '基础动画']} />
<Row className="mb-m">
<span className="mr-s">()</span>
<Switch onChange={this.animatedAll} />
</Row>
<Row gutter={14}>
{animations.map((v, i) => (
<Col className="gutter-row" md={6} key={i}>
<div className="gutter-box">
<Card
className={`${
this.state.animated || this.state.animatedOne === i
? 'animated'
: ''
} ${
this.state.animated || this.state.animatedOne === i
? 'infinite'
: ''
} ${v}`}
onMouseEnter={() => this.animatedOne(i)}
onMouseLeave={() => this.animatedOneOver()}
>
<div className="pa-m text-center">
<h3>{v}</h3>
</div>
</Card>
</div>
</Col>
))}
</Row>
</div>
);
}
}
export default BasicAnimations;

View File

@@ -0,0 +1,120 @@
/**
* Created by hao.cheng on 2017/5/8.
*/
import React from 'react';
import { Row, Col, Card, Table, Popconfirm, Button } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
type ExampleAnimationsProps = {};
type ExampleAnimationsState = {
dataSource: any;
count: number;
deleteIndex: number;
};
class ExampleAnimations extends React.Component<ExampleAnimationsProps, ExampleAnimationsState> {
constructor(props: any) {
super(props);
this.columns = [
{
title: 'name',
dataIndex: 'name',
width: '30%',
},
{
title: 'age',
dataIndex: 'age',
},
{
title: 'address',
dataIndex: 'address',
},
{
title: 'operation',
dataIndex: 'operation',
render: (text: any, record: any, index: number) => {
return this.state.dataSource.length > 1 ? (
<Popconfirm
title="Sure to delete?"
onConfirm={() => this.onDelete(record, index)}
>
<span>Delete</span>
</Popconfirm>
) : null;
},
},
];
this.state = {
dataSource: [
{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
},
{
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
},
],
count: 2,
deleteIndex: -1,
};
}
columns: any;
onDelete = (record: any, index: number) => {
const dataSource = [...this.state.dataSource];
dataSource.splice(index, 1);
this.setState({ deleteIndex: record.key });
setTimeout(() => {
this.setState({ dataSource });
}, 500);
};
handleAdd = () => {
const { count, dataSource } = this.state;
const newData = {
key: count,
name: `Edward King ${count}`,
age: 32,
address: `London, Park Lane no. ${count}`,
};
this.setState({
dataSource: [newData, ...dataSource],
count: count + 1,
});
};
render() {
const { dataSource } = this.state;
const columns = this.columns;
return (
<div className="gutter-example">
<BreadcrumbCustom breads={['动画', '动画案例']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card bordered={false}>
<Button className="editable-add-btn mb-s" onClick={this.handleAdd}>
Add
</Button>
<Table
bordered
dataSource={dataSource}
columns={columns}
rowClassName={(record: any, index: number) => {
if (this.state.deleteIndex === record.key)
return 'animated zoomOutLeft min-black';
return 'animated fadeInRight';
}}
/>
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default ExampleAnimations;

View File

@@ -0,0 +1,62 @@
/**
* Created by 叶子 on 2017/7/31.
*/
import React, { Component } from 'react';
import { Row, Col, Card } from 'antd';
import beauty from '@/style/imgs/beauty.jpg';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { AuthWidget } from '../widget';
class Basic extends Component {
render() {
return (
<div>
<BreadcrumbCustom breads={['权限管理', '基础演示']} />
<AuthWidget
children={(auth: any) => (
<Row>
<Col span={24}>
<Card bordered={false} bodyStyle={{ minHeight: 600 }}>
{!auth.uid && (
<h2 style={{ height: 500 }} className="center">
</h2>
)}
{auth.permissions &&
auth.permissions.includes('auth/authPage/visit') && (
<div style={{ textAlign: 'center' }}>
<img src={beauty} alt="" style={{ height: 400 }} />
{(auth.permissions.includes(
'auth/authPage/edit'
) && (
<div>
<p>
~
<span
role="img"
aria-label=""
aria-labelledby=""
>
😄😄
</span>
</p>
<p></p>
</div>
)) || (
<div>
<p></p>
</div>
)}
</div>
)}
</Card>
</Col>
</Row>
)}
/>
</div>
);
}
}
export default Basic;

View File

@@ -0,0 +1,38 @@
/**
* Created by 叶子 on 2017/8/1.
*/
/**
* Created by 叶子 on 2017/7/31.
*/
import React, { Component } from 'react';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { AuthWidget } from '../widget';
class RouterEnter extends Component {
componentDidMount() {
console.log('RouterEnter');
}
render() {
return (
<div>
<BreadcrumbCustom breads={['权限管理', '路由拦截']} />
<AuthWidget
children={(auth: any) => (
<Row>
<Col span={24}>
<Card bordered={false} bodyStyle={{ minHeight: 600 }}>
<h2 style={{ height: 500 }} className="center">
404
</h2>
</Card>
</Col>
</Row>
)}
/>
</div>
);
}
}
export default RouterEnter;

View File

@@ -0,0 +1,55 @@
/**
* Created by hao.cheng on 2017/4/17.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import EchartsArea from './EchartsArea';
import EchartsPie from './EchartsPie';
import EchartsEffectScatter from './EchartsEffectScatter';
import EchartsForce from './EchartsForce';
class Echarts extends React.Component {
render() {
return (
<div className="gutter-example">
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="区域图" bordered={false}>
<EchartsArea />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="关系图" bordered={false}>
{/*<EchartsGraphnpm />*/}
<EchartsForce />
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="饼图" bordered={false}>
<EchartsPie />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="散点图" bordered={false}>
<EchartsEffectScatter />
</Card>
</div>
</Col>
</Row>
</div>
)
}
}
export default Echarts;

View File

@@ -0,0 +1,113 @@
/**
* Created by hao.cheng on 2017/4/17.
*/
import React from 'react';
import ReactEcharts from 'echarts-for-react';
import echarts from 'echarts';
let base = +new Date(1968, 9, 3);
let oneDay = 24 * 3600 * 1000;
let date = [];
let data = [Math.random() * 300];
for (var i = 1; i < 20000; i++) {
var now = new Date((base += oneDay));
date.push([now.getFullYear(), now.getMonth() + 1, now.getDate()].join('/'));
data.push(Math.round((Math.random() - 0.5) * 20 + data[i - 1]));
}
const option = {
tooltip: {
trigger: 'axis',
position: function(pt: any) {
return [pt[0], '10%'];
},
},
title: {
left: 'center',
text: '大数据量面积图',
},
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none',
},
restore: {},
saveAsImage: {},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: date,
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 10,
},
{
start: 0,
end: 10,
handleIcon:
'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
handleSize: '80%',
handleStyle: {
color: '#fff',
shadowBlur: 3,
shadowColor: 'rgba(0, 0, 0, 0.6)',
shadowOffsetX: 2,
shadowOffsetY: 2,
},
},
],
series: [
{
name: '模拟数据',
type: 'line',
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: {
normal: {
color: 'rgb(255, 70, 131)',
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 158, 68)',
},
{
offset: 1,
color: 'rgb(255, 70, 131)',
},
]),
},
},
data: data,
},
],
};
class EchartsArea extends React.Component {
render() {
return (
<ReactEcharts
option={option}
style={{ height: '300px', width: '100%' }}
className={'react_for_echarts'}
/>
);
}
}
export default EchartsArea;

View File

@@ -0,0 +1,522 @@
/**
* Created by SEELE on 2017/8/23.
*/
import React, { Component } from 'react';
import ReactEcharts from 'echarts-for-react';
require('echarts/map/js/china.js');
const data = [
{ name: '海门', value: 9 },
{ name: '鄂尔多斯', value: 12 },
{ name: '招远', value: 12 },
{ name: '舟山', value: 12 },
{ name: '齐齐哈尔', value: 14 },
{ name: '盐城', value: 15 },
{ name: '赤峰', value: 16 },
{ name: '青岛', value: 18 },
{ name: '乳山', value: 18 },
{ name: '金昌', value: 19 },
{ name: '泉州', value: 21 },
{ name: '莱西', value: 21 },
{ name: '日照', value: 21 },
{ name: '胶南', value: 22 },
{ name: '南通', value: 23 },
{ name: '拉萨', value: 24 },
{ name: '云浮', value: 24 },
{ name: '梅州', value: 25 },
{ name: '文登', value: 25 },
{ name: '上海', value: 25 },
{ name: '攀枝花', value: 25 },
{ name: '威海', value: 25 },
{ name: '承德', value: 25 },
{ name: '厦门', value: 26 },
{ name: '汕尾', value: 26 },
{ name: '潮州', value: 26 },
{ name: '丹东', value: 27 },
{ name: '太仓', value: 27 },
{ name: '曲靖', value: 27 },
{ name: '烟台', value: 28 },
{ name: '福州', value: 29 },
{ name: '瓦房店', value: 30 },
{ name: '即墨', value: 30 },
{ name: '抚顺', value: 31 },
{ name: '玉溪', value: 31 },
{ name: '张家口', value: 31 },
{ name: '阳泉', value: 31 },
{ name: '莱州', value: 32 },
{ name: '湖州', value: 32 },
{ name: '汕头', value: 32 },
{ name: '昆山', value: 33 },
{ name: '宁波', value: 33 },
{ name: '湛江', value: 33 },
{ name: '揭阳', value: 34 },
{ name: '荣成', value: 34 },
{ name: '连云港', value: 35 },
{ name: '葫芦岛', value: 35 },
{ name: '常熟', value: 36 },
{ name: '东莞', value: 36 },
{ name: '河源', value: 36 },
{ name: '淮安', value: 36 },
{ name: '泰州', value: 36 },
{ name: '南宁', value: 37 },
{ name: '营口', value: 37 },
{ name: '惠州', value: 37 },
{ name: '江阴', value: 37 },
{ name: '蓬莱', value: 37 },
{ name: '韶关', value: 38 },
{ name: '嘉峪关', value: 38 },
{ name: '广州', value: 38 },
{ name: '延安', value: 38 },
{ name: '太原', value: 39 },
{ name: '清远', value: 39 },
{ name: '中山', value: 39 },
{ name: '昆明', value: 39 },
{ name: '寿光', value: 40 },
{ name: '盘锦', value: 40 },
{ name: '长治', value: 41 },
{ name: '深圳', value: 41 },
{ name: '珠海', value: 42 },
{ name: '宿迁', value: 43 },
{ name: '咸阳', value: 43 },
{ name: '铜川', value: 44 },
{ name: '平度', value: 44 },
{ name: '佛山', value: 44 },
{ name: '海口', value: 44 },
{ name: '江门', value: 45 },
{ name: '章丘', value: 45 },
{ name: '肇庆', value: 46 },
{ name: '大连', value: 47 },
{ name: '临汾', value: 47 },
{ name: '吴江', value: 47 },
{ name: '石嘴山', value: 49 },
{ name: '沈阳', value: 50 },
{ name: '苏州', value: 50 },
{ name: '茂名', value: 50 },
{ name: '嘉兴', value: 51 },
{ name: '长春', value: 51 },
{ name: '胶州', value: 52 },
{ name: '银川', value: 52 },
{ name: '张家港', value: 52 },
{ name: '三门峡', value: 53 },
{ name: '锦州', value: 54 },
{ name: '南昌', value: 54 },
{ name: '柳州', value: 54 },
{ name: '三亚', value: 54 },
{ name: '自贡', value: 56 },
{ name: '吉林', value: 56 },
{ name: '阳江', value: 57 },
{ name: '泸州', value: 57 },
{ name: '西宁', value: 57 },
{ name: '宜宾', value: 58 },
{ name: '呼和浩特', value: 58 },
{ name: '成都', value: 58 },
{ name: '大同', value: 58 },
{ name: '镇江', value: 59 },
{ name: '桂林', value: 59 },
{ name: '张家界', value: 59 },
{ name: '宜兴', value: 59 },
{ name: '北海', value: 60 },
{ name: '西安', value: 61 },
{ name: '金坛', value: 62 },
{ name: '东营', value: 62 },
{ name: '牡丹江', value: 63 },
{ name: '遵义', value: 63 },
{ name: '绍兴', value: 63 },
{ name: '扬州', value: 64 },
{ name: '常州', value: 64 },
{ name: '潍坊', value: 65 },
{ name: '重庆', value: 66 },
{ name: '台州', value: 67 },
{ name: '南京', value: 67 },
{ name: '滨州', value: 70 },
{ name: '贵阳', value: 71 },
{ name: '无锡', value: 71 },
{ name: '本溪', value: 71 },
{ name: '克拉玛依', value: 72 },
{ name: '渭南', value: 72 },
{ name: '马鞍山', value: 72 },
{ name: '宝鸡', value: 72 },
{ name: '焦作', value: 75 },
{ name: '句容', value: 75 },
{ name: '北京', value: 79 },
{ name: '徐州', value: 79 },
{ name: '衡水', value: 80 },
{ name: '包头', value: 80 },
{ name: '绵阳', value: 80 },
{ name: '乌鲁木齐', value: 84 },
{ name: '枣庄', value: 84 },
{ name: '杭州', value: 84 },
{ name: '淄博', value: 85 },
{ name: '鞍山', value: 86 },
{ name: '溧阳', value: 86 },
{ name: '库尔勒', value: 86 },
{ name: '安阳', value: 90 },
{ name: '开封', value: 90 },
{ name: '济南', value: 92 },
{ name: '德阳', value: 93 },
{ name: '温州', value: 95 },
{ name: '九江', value: 96 },
{ name: '邯郸', value: 98 },
{ name: '临安', value: 99 },
{ name: '兰州', value: 99 },
{ name: '沧州', value: 100 },
{ name: '临沂', value: 103 },
{ name: '南充', value: 104 },
{ name: '天津', value: 105 },
{ name: '富阳', value: 106 },
{ name: '泰安', value: 112 },
{ name: '诸暨', value: 112 },
{ name: '郑州', value: 113 },
{ name: '哈尔滨', value: 114 },
{ name: '聊城', value: 116 },
{ name: '芜湖', value: 117 },
{ name: '唐山', value: 119 },
{ name: '平顶山', value: 119 },
{ name: '邢台', value: 119 },
{ name: '德州', value: 120 },
{ name: '济宁', value: 120 },
{ name: '荆州', value: 127 },
{ name: '宜昌', value: 130 },
{ name: '义乌', value: 132 },
{ name: '丽水', value: 133 },
{ name: '洛阳', value: 134 },
{ name: '秦皇岛', value: 136 },
{ name: '株洲', value: 143 },
{ name: '石家庄', value: 147 },
{ name: '莱芜', value: 148 },
{ name: '常德', value: 152 },
{ name: '保定', value: 153 },
{ name: '湘潭', value: 154 },
{ name: '金华', value: 157 },
{ name: '岳阳', value: 169 },
{ name: '长沙', value: 175 },
{ name: '衢州', value: 177 },
{ name: '廊坊', value: 193 },
{ name: '菏泽', value: 194 },
{ name: '合肥', value: 229 },
{ name: '武汉', value: 273 },
{ name: '大庆', value: 279 },
];
const geoCoordMap = {
: [121.15, 31.89],
: [109.781327, 39.608266],
: [120.38, 37.35],
: [122.207216, 29.985295],
: [123.97, 47.33],
: [120.13, 33.38],
: [118.87, 42.28],
: [120.33, 36.07],
: [121.52, 36.89],
: [102.188043, 38.520089],
: [118.58, 24.93],
西: [120.53, 36.86],
: [119.46, 35.42],
: [119.97, 35.88],
: [121.05, 32.08],
: [91.11, 29.97],
: [112.02, 22.93],
: [116.1, 24.55],
: [122.05, 37.2],
: [121.48, 31.22],
: [101.718637, 26.582347],
: [122.1, 37.5],
: [117.93, 40.97],
: [118.1, 24.46],
: [115.375279, 22.786211],
: [116.63, 23.68],
: [124.37, 40.13],
: [121.1, 31.45],
: [103.79, 25.51],
: [121.39, 37.52],
: [119.3, 26.08],
: [121.979603, 39.627114],
: [120.45, 36.38],
: [123.97, 41.97],
: [102.52, 24.35],
: [114.87, 40.82],
: [113.57, 37.85],
: [119.942327, 37.177017],
: [120.1, 30.86],
: [116.69, 23.39],
: [120.95, 31.39],
: [121.56, 29.86],
: [110.359377, 21.270708],
: [116.35, 23.55],
: [122.41, 37.16],
: [119.16, 34.59],
: [120.836932, 40.711052],
: [120.74, 31.64],
: [113.75, 23.04],
: [114.68, 23.73],
: [119.15, 33.5],
: [119.9, 32.49],
: [108.33, 22.84],
: [122.18, 40.65],
: [114.4, 23.09],
: [120.26, 31.91],
: [120.75, 37.8],
: [113.62, 24.84],
: [98.289152, 39.77313],
广: [113.23, 23.16],
: [109.47, 36.6],
: [112.53, 37.87],
: [113.01, 23.7],
: [113.38, 22.52],
: [102.73, 25.04],
寿: [118.73, 36.86],
: [122.070714, 41.119997],
: [113.08, 36.18],
: [114.07, 22.62],
: [113.52, 22.3],
宿: [118.3, 33.96],
: [108.72, 34.36],
: [109.11, 35.09],
: [119.97, 36.77],
: [113.11, 23.05],
: [110.35, 20.02],
: [113.06, 22.61],
: [117.53, 36.72],
: [112.44, 23.05],
: [121.62, 38.92],
: [111.5, 36.08],
: [120.63, 31.16],
: [106.39, 39.04],
: [123.38, 41.8],
: [120.62, 31.32],
: [110.88, 21.68],
: [120.76, 30.77],
: [125.35, 43.88],
: [120.03336, 36.264622],
: [106.27, 38.47],
: [120.555821, 31.875428],
: [111.19, 34.76],
: [121.15, 41.13],
: [115.89, 28.68],
: [109.4, 24.33],
: [109.511909, 18.252847],
: [104.778442, 29.33903],
: [126.57, 43.87],
: [111.95, 21.85],
: [105.39, 28.91],
西: [101.74, 36.56],
: [104.56, 29.77],
: [111.65, 40.82],
: [104.06, 30.67],
: [113.3, 40.12],
: [119.44, 32.2],
: [110.28, 25.29],
: [110.479191, 29.117096],
: [119.82, 31.36],
: [109.12, 21.49],
西: [108.95, 34.27],
: [119.56, 31.74],
: [118.49, 37.46],
: [129.58, 44.6],
: [106.9, 27.7],
: [120.58, 30.01],
: [119.42, 32.39],
: [119.95, 31.79],
: [119.1, 36.62],
: [106.54, 29.59],
: [121.420757, 28.656386],
: [118.78, 32.04],
: [118.03, 37.36],
: [106.71, 26.57],
: [120.29, 31.59],
: [123.73, 41.3],
: [84.77, 45.59],
: [109.5, 34.52],
: [118.48, 31.56],
: [107.15, 34.38],
: [113.21, 35.24],
: [119.16, 31.95],
: [116.46, 39.92],
: [117.2, 34.26],
: [115.72, 37.72],
: [110, 40.58],
: [104.73, 31.48],
: [87.68, 43.77],
: [117.57, 34.86],
: [120.19, 30.26],
: [118.05, 36.78],
: [122.85, 41.12],
: [119.48, 31.43],
: [86.06, 41.68],
: [114.35, 36.1],
: [114.35, 34.79],
: [117, 36.65],
: [104.37, 31.13],
: [120.65, 28.01],
: [115.97, 29.71],
: [114.47, 36.6],
: [119.72, 30.23],
: [103.73, 36.03],
: [116.83, 38.33],
: [118.35, 35.05],
: [106.110698, 30.837793],
: [117.2, 39.13],
: [119.95, 30.07],
: [117.13, 36.18],
: [120.23, 29.71],
: [113.65, 34.76],
: [126.63, 45.75],
: [115.97, 36.45],
: [118.38, 31.33],
: [118.02, 39.63],
: [113.29, 33.75],
: [114.48, 37.05],
: [116.29, 37.45],
: [116.59, 35.38],
: [112.239741, 30.335165],
: [111.3, 30.7],
: [120.06, 29.32],
: [119.92, 28.45],
: [112.44, 34.7],
: [119.57, 39.95],
: [113.16, 27.83],
: [114.48, 38.03],
: [117.67, 36.19],
: [111.69, 29.05],
: [115.48, 38.85],
: [112.91, 27.87],
: [119.64, 29.12],
: [113.09, 29.37],
: [113, 28.21],
: [118.88, 28.97],
: [116.7, 39.53],
: [115.480656, 35.23375],
: [117.27, 31.86],
: [114.31, 30.52],
: [125.03, 46.58],
};
const convertData = function(data: any) {
let res = [];
for (let i = 0; i < data.length; i++) {
let geoCoord = (geoCoordMap as any)[data[i].name];
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value),
});
}
}
return res;
};
const option = {
backgroundColor: '#404a59',
title: {
text: '全国主要城市空气质量',
subtext: 'data from PM25.in',
sublink: 'http://www.pm25.in',
left: 'center',
textStyle: {
color: '#fff',
},
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
y: 'bottom',
x: 'right',
data: ['pm2.5'],
textStyle: {
color: '#fff',
},
},
geo: {
map: 'china',
label: {
emphasis: {
show: false,
},
},
roam: true,
itemStyle: {
normal: {
areaColor: '#323c48',
borderColor: '#111',
},
emphasis: {
areaColor: '#2a333d',
},
},
},
series: [
{
name: 'pm2.5',
type: 'scatter',
coordinateSystem: 'geo',
data: convertData(data),
symbolSize: function(val: any) {
return val[2] / 10;
},
label: {
normal: {
formatter: '{b}',
position: 'right',
show: false,
},
emphasis: {
show: true,
},
},
itemStyle: {
normal: {
color: '#ddb926',
},
},
},
{
name: 'Top 5',
type: 'effectScatter',
coordinateSystem: 'geo',
data: convertData(
data
.sort(function(a, b) {
return b.value - a.value;
})
.slice(0, 6)
),
symbolSize: function(val: any) {
return val[2] / 10;
},
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
},
hoverAnimation: true,
label: {
normal: {
formatter: '{b}',
position: 'right',
show: true,
},
},
itemStyle: {
normal: {
color: '#f4e925',
shadowBlur: 10,
shadowColor: '#333',
},
},
zlevel: 1,
},
],
};
class EchartsEffectScatter extends Component {
render() {
return (
<ReactEcharts
option={option}
style={{ height: '400px', width: '100%' }}
className={'react_for_echarts'}
/>
);
}
}
export default EchartsEffectScatter;

View File

@@ -0,0 +1,247 @@
/**
* Created by SEELE on 2017/8/23.
*/
import React, { Component } from 'react';
import ReactEcharts from 'echarts-for-react';
const option = {
title: {
text: '',
},
tooltip: {},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
label: {
normal: {
show: true,
textStyle: {
fontSize: 12,
},
},
},
legend: {
x: 'center',
show: false,
data: ['朋友', '战友', '亲戚'],
},
series: [
{
type: 'graph',
layout: 'force',
symbolSize: 45,
focusNodeAdjacency: true,
roam: true,
categories: [
{
name: '朋友',
itemStyle: {
normal: {
color: '#009800',
},
},
},
{
name: '战友',
itemStyle: {
normal: {
color: '#4592FF',
},
},
},
{
name: '亲戚',
itemStyle: {
normal: {
color: '#3592F',
},
},
},
],
label: {
normal: {
show: true,
textStyle: {
fontSize: 12,
},
},
},
force: {
repulsion: 1000,
},
edgeSymbolSize: [4, 50],
edgeLabel: {
normal: {
show: true,
textStyle: {
fontSize: 10,
},
formatter: '{c}',
},
},
data: [
{
name: '徐贱云',
draggable: true,
},
{
name: '冯可梁',
category: 1,
draggable: true,
},
{
name: '邓志荣',
category: 1,
draggable: true,
},
{
name: '李荣庆',
category: 1,
draggable: true,
},
{
name: '郑志勇',
category: 1,
draggable: true,
},
{
name: '赵英杰',
category: 1,
draggable: true,
},
{
name: '王承军',
category: 1,
draggable: true,
},
{
name: '陈卫东',
category: 1,
draggable: true,
},
{
name: '邹劲松',
category: 1,
draggable: true,
},
{
name: '赵成',
category: 1,
draggable: true,
},
{
name: '陈现忠',
category: 1,
draggable: true,
},
{
name: '陶泳',
category: 1,
draggable: true,
},
{
name: '王德福',
category: 1,
draggable: true,
},
],
links: [
{
source: 0,
target: 1,
category: 0,
value: '朋友',
},
{
source: 0,
target: 2,
value: '战友',
},
{
source: 0,
target: 3,
value: '房东',
},
{
source: 0,
target: 4,
value: '朋友',
},
{
source: 1,
target: 2,
value: '表亲',
},
{
source: 0,
target: 5,
value: '朋友',
},
{
source: 4,
target: 5,
value: '姑姑',
},
{
source: 2,
target: 8,
value: '叔叔',
},
{
source: 0,
target: 12,
value: '朋友',
},
{
source: 6,
target: 11,
value: '爱人',
},
{
source: 6,
target: 3,
value: '朋友',
},
{
source: 7,
target: 5,
value: '朋友',
},
{
source: 9,
target: 10,
value: '朋友',
},
{
source: 3,
target: 10,
value: '朋友',
},
{
source: 2,
target: 11,
value: '同学',
},
],
lineStyle: {
normal: {
opacity: 0.9,
width: 1,
curveness: 0,
},
},
},
],
};
class EchartsForce extends Component {
render() {
return (
<ReactEcharts
option={option}
style={{ height: '400px', width: '100%' }}
className={'react_for_echarts'}
/>
);
}
}
export default EchartsForce;

View File

@@ -0,0 +1,85 @@
/**
* Created by hao.cheng on 2017/4/21.
*/
import React from 'react';
import ReactEcharts from 'echarts-for-react';
import { npmDependencies } from '../../service';
class EchartsGraphnpm extends React.Component {
state = {
option: {
title: {
text: 'NPM Dependencies',
},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
series: [
{
type: 'graph',
layout: 'none',
// progressiveThreshold: 700,
data: [],
edges: [],
label: {
emphasis: {
position: 'right',
show: true,
},
},
roam: true,
focusNodeAdjacency: true,
lineStyle: {
normal: {
width: 0.5,
curveness: 0.3,
opacity: 0.7,
},
},
},
],
},
};
componentDidMount() {
npmDependencies().then((npm) => {
this.setState({
option: {
series: [
{
data: npm.nodes.map(function (node: any) {
return {
x: node.x,
y: node.y,
id: node.id,
name: node.label,
symbolSize: node.size,
itemStyle: {
normal: {
color: node.color,
},
},
};
}),
edges: npm.edges.map(function (edge: any) {
return {
source: edge.sourceID,
target: edge.targetID,
};
}),
},
],
},
});
});
}
render() {
return (
<ReactEcharts
option={this.state.option}
style={{ height: '300px', width: '100%' }}
className={'react_for_echarts'}
/>
);
}
}
export default EchartsGraphnpm;

View File

@@ -0,0 +1,88 @@
/**
* Created by hao.cheng on 2017/4/21.
*/
import React from 'react';
import ReactEcharts from 'echarts-for-react';
const option = {
title: {
text: 'Customized Pie',
left: 'center',
top: 20,
textStyle: {
color: '#777',
},
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
visualMap: {
show: false,
min: 80,
max: 600,
inRange: {
colorLightness: [0, 1],
},
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '55%',
center: ['50%', '50%'],
data: [
{ value: 335, name: '直接访问' },
{ value: 310, name: '邮件营销' },
{ value: 274, name: '联盟广告' },
{ value: 235, name: '视频广告' },
{ value: 400, name: '搜索引擎' },
].sort(function(a, b) {
return a.value - b.value;
}),
roseType: 'angle',
label: {
normal: {
textStyle: {
color: '#777',
},
},
},
labelLine: {
normal: {
lineStyle: {
color: '#777',
},
smooth: 0.2,
length: 10,
length2: 20,
},
},
itemStyle: {
normal: {
color: '#c23531',
shadowBlur: 200,
shadowColor: '#777',
},
},
animationType: 'scale',
animationEasing: 'elasticOut',
animationDelay: function(idx: any) {
return Math.random() * 200;
},
},
],
};
const EchartsPie = () => (
<ReactEcharts
option={option}
style={{ height: '300px', width: '100%' }}
className={'react_for_echarts'}
/>
);
export default EchartsPie;

View File

@@ -0,0 +1,138 @@
/**
* Created by hao.cheng on 2017/4/21.
*/
import React from 'react';
import ReactEcharts from 'echarts-for-react';
import { weibo } from '../../service';
require('echarts/map/js/china.js');
class EchartsScatter extends React.Component {
state = {
option: {
backgroundColor: '#404a59',
title: {
text: '微博签到数据点亮中国',
subtext: 'From ThinkGIS',
sublink: 'http://www.thinkgis.cn/public/sina',
left: 'center',
top: 'top',
textStyle: {
color: '#fff',
},
},
tooltip: {},
legend: {
left: 'left',
data: ['强', '中', '弱'],
textStyle: {
color: '#ccc',
},
},
geo: {
map: 'china',
label: {
emphasis: {
show: false,
},
},
itemStyle: {
normal: {
areaColor: '#323c48',
borderColor: '#111',
},
emphasis: {
areaColor: '#2a333d',
},
},
},
series: [
{
name: '弱',
type: 'scatter',
coordinateSystem: 'geo',
symbolSize: 1,
large: true,
itemStyle: {
normal: {
shadowBlur: 2,
shadowColor: 'rgba(37, 140, 249, 0.8)',
color: 'rgba(37, 140, 249, 0.8)',
},
},
data: [],
},
{
name: '中',
type: 'scatter',
coordinateSystem: 'geo',
symbolSize: 1,
large: true,
itemStyle: {
normal: {
shadowBlur: 2,
shadowColor: 'rgba(14, 241, 242, 0.8)',
color: 'rgba(14, 241, 242, 0.8)',
},
},
data: [],
},
{
name: '强',
type: 'scatter',
coordinateSystem: 'geo',
symbolSize: 1,
large: true,
itemStyle: {
normal: {
shadowBlur: 2,
shadowColor: 'rgba(255, 255, 255, 0.8)',
color: 'rgba(255, 255, 255, 0.8)',
},
},
data: [],
},
],
},
};
componentDidMount() {
weibo().then((weiboData) => {
weiboData = weiboData.map(function (serieData: any) {
var px = serieData[0] / 1000;
var py = serieData[1] / 1000;
var res = [[px, py]];
for (var i = 2; i < serieData.length; i += 2) {
var dx = serieData[i] / 1000;
var dy = serieData[i + 1] / 1000;
var x = px + dx;
var y = py + dy;
res.push([parseInt(x.toFixed(2), 10), parseInt(y.toFixed(2), 10), 1]);
px = x;
py = y;
}
return res;
});
this.setState({
option: {
series: [
{ data: weiboData[0] },
{ data: weiboData[1] },
{ data: weiboData[2] },
],
},
});
});
}
render() {
return (
<ReactEcharts
option={this.state.option}
style={{ height: '400px', width: '100%' }}
className={'react_for_echarts'}
/>
);
}
}
export default EchartsScatter;

View File

@@ -0,0 +1,54 @@
/**
* Created by hao.cheng on 2017/4/21.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import RechartsSimpleLineChart from './RechartsSimpleLineChart';
import RechartsBarChart from './RechartsBarChart';
import RechartsRadialBarChart from './RechartsRadialBarChart';
import RechartsRadarChart from './RechartsRadarChart';
class Recharts extends React.Component {
render() {
return (
<div className="gutter-example">
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="基础线形图" bordered={false}>
<RechartsSimpleLineChart />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="基础线形图" bordered={false}>
<RechartsBarChart />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="基础线形图" bordered={false}>
<RechartsRadialBarChart />
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="基础线形图" bordered={false}>
<RechartsRadarChart />
</Card>
</div>
</Col>
</Row>
</div>
)
}
}
export default Recharts;

View File

@@ -0,0 +1,34 @@
/**
* Created by hao.cheng on 2017/4/21.
*/
import React from 'react';
import {BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer} from 'recharts';
const data = [
{name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
{name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
{name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
{name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
{name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
{name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
{name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
];
const RechartsBarChart = () => (
<ResponsiveContainer width="100%" height={300}>
<BarChart
data={data}
margin={{top: 5, right: 30, left: 20, bottom: 5}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip />
<Legend />
<Bar dataKey="pv" fill="#8884d8" />
<Bar dataKey="uv" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
);
export default RechartsBarChart;

View File

@@ -0,0 +1,30 @@
/**
* Created by hao.cheng on 2017/4/22.
*/
import React from 'react';
import {Radar, RadarChart, PolarGrid, Legend,
PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer} from 'recharts';
const data = [
{ subject: 'Math', A: 120, B: 110, fullMark: 150 },
{ subject: 'Chinese', A: 98, B: 130, fullMark: 150 },
{ subject: 'English', A: 86, B: 130, fullMark: 150 },
{ subject: 'Geography', A: 99, B: 100, fullMark: 150 },
{ subject: 'Physics', A: 85, B: 90, fullMark: 150 },
{ subject: 'History', A: 65, B: 85, fullMark: 150 },
];
const RechartsRadarChart = () => (
<ResponsiveContainer width="100%" height={300} >
<RadarChart outerRadius={90} data={data}>
<Radar name="Mike" dataKey="A" stroke="#8884d8" fill="#8884d8" fillOpacity={0.6} />
<Radar name="Lily" dataKey="B" stroke="#82ca9d" fill="#82ca9d" fillOpacity={0.6} />
<PolarGrid />
<Legend />
<PolarAngleAxis dataKey="subject" />
<PolarRadiusAxis angle={30} domain={[0, 150]} />
</RadarChart>
</ResponsiveContainer>
);
export default RechartsRadarChart;

View File

@@ -0,0 +1,42 @@
/**
* Created by hao.cheng on 2017/4/22.
*/
import React from 'react';
import { RadialBarChart, Legend, Tooltip, ResponsiveContainer } from 'recharts';
const data = [
{ name: '18-24', uv: 31.47, pv: 2400, fill: '#8884d8' },
{ name: '25-29', uv: 26.69, pv: 4567, fill: '#83a6ed' },
{ name: '30-34', uv: 15.69, pv: 1398, fill: '#8dd1e1' },
{ name: '35-39', uv: 8.22, pv: 9800, fill: '#82ca9d' },
{ name: '40-49', uv: 8.63, pv: 3908, fill: '#a4de6c' },
{ name: '50+', uv: 2.63, pv: 4800, fill: '#d0ed57' },
{ name: 'unknow', uv: 6.67, pv: 4800, fill: '#ffc658' },
];
const RechartsRadialBarChart = () => (
<ResponsiveContainer width="100%" height={300}>
<RadialBarChart width={730} height={250} innerRadius="10%" outerRadius="80%" data={data}>
{/* <RadialBar
startAngle={90}
endAngle={-270}
minAngle={15}
label
background
clockWise
dataKey="uv"
/> */}
<Legend
iconSize={10}
width={120}
height={140}
layout="vertical"
verticalAlign="middle"
align="right"
/>
<Tooltip />
</RadialBarChart>
</ResponsiveContainer>
);
export default RechartsRadialBarChart;

View File

@@ -0,0 +1,36 @@
/**
* Created by hao.cheng on 2017/4/21.
*/
import React from 'react';
import {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer} from 'recharts';
const data = [
{name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
{name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
{name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
{name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
{name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
{name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
{name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
];
const RechartsSimpleLineChart = () => (
<ResponsiveContainer width="100%" height={300}>
<LineChart
data={data}
margin={{top: 5, right: 30, left: 20, bottom: 5}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{r: 8}} />
<Line type="monotone" dataKey="uv" stroke="#82ca9d" />
</LineChart>
</ResponsiveContainer>
);
export default RechartsSimpleLineChart;

View File

@@ -0,0 +1,32 @@
@font-face {
font-family: 'Monoton';
font-style: normal;
font-weight: 400;
src: local('Monoton'), local('Monoton-Regular'), url(../../style/font/y6oxFxU60dYw9khW6q8jGw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
}
.header {
font-size: 7em;
width: 100%;
height: 500px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-family: Monoton;
p {
animation: neon1 1.5s ease-in-out infinite alternate;
&:hover {
color: #FF1177;
animation: none;
}
}
}
@keyframes neon1 {
from {
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #FF1177, 0 0 70px #FF1177, 0 0 80px #FF1177, 0 0 100px #FF1177, 0 0 150px #FF1177;
}
to {
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #FF1177, 0 0 35px #FF1177, 0 0 40px #FF1177, 0 0 50px #FF1177, 0 0 75px #FF1177;
}
}

View File

@@ -0,0 +1,31 @@
/**
*
* 添加注释
* Created by SEELE on 2018/1/12
*
*/
import React, { Component } from 'react';
import { Col, Card, Row } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import styles from './index.module.less';
class Cssmodule extends Component {
render() {
return (
<div>
<BreadcrumbCustom breads={['cssModule']} />
<Row gutter={16}>
<Col md={24}>
<Card title="cssModule" bordered={false}>
<div className={styles.header}>
<p>Hello CssModule</p>
</div>
</Card>
</Col>
</Row>
</div>
);
}
}
export default Cssmodule;

View File

@@ -0,0 +1,204 @@
/**
* Created by hao.cheng on 2017/5/3.
*/
import React from 'react';
import { Row, Col, Card, Timeline } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import EchartsViews from './EchartsViews';
import EchartsProjects from './EchartsProjects';
import b1 from '../../style/imgs/b1.jpg';
import {
CameraOutlined,
CloudOutlined,
HeartOutlined,
MailOutlined,
SyncOutlined,
} from '@ant-design/icons';
class Dashboard extends React.Component {
render() {
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom />
<Row gutter={10}>
<Col className="gutter-row" md={4}>
<div className="gutter-box">
<Card bordered={false}>
<div className="clear y-center">
<div className="pull-left mr-m">
<HeartOutlined className="text-2x text-danger" />
</div>
<div className="clear">
<div className="text-muted"></div>
<h2>301</h2>
</div>
</div>
</Card>
</div>
<div className="gutter-box">
<Card bordered={false}>
<div className="clear y-center">
<div className="pull-left mr-m">
<CloudOutlined type="cloud" className="text-2x" />
</div>
<div className="clear">
<div className="text-muted"></div>
<h2>30122</h2>
</div>
</div>
</Card>
</div>
</Col>
<Col className="gutter-row" md={4}>
<div className="gutter-box">
<Card bordered={false}>
<div className="clear y-center">
<div className="pull-left mr-m">
<CameraOutlined className="text-2x text-info" />
</div>
<div className="clear">
<div className="text-muted"></div>
<h2>802</h2>
</div>
</div>
</Card>
</div>
<div className="gutter-box">
<Card bordered={false}>
<div className="clear y-center">
<div className="pull-left mr-m">
<MailOutlined className="text-2x text-success" />
</div>
<div className="clear">
<div className="text-muted"></div>
<h2>102</h2>
</div>
</div>
</Card>
</div>
</Col>
<Col className="gutter-row" md={16}>
<div className="gutter-box">
<Card bordered={false} className={'no-padding'}>
<EchartsProjects />
</Card>
</div>
</Col>
</Row>
<Row gutter={10}>
<Col className="gutter-row" md={8}>
<div className="gutter-box">
<Card bordered={false}>
<div className="pb-m">
<h3></h3>
<small>1021</small>
</div>
<span className="card-tool">
<SyncOutlined />
</span>
<Timeline>
<Timeline.Item color="green"></Timeline.Item>
<Timeline.Item color="green"></Timeline.Item>
<Timeline.Item color="red">
<p></p>
<p></p>
</Timeline.Item>
<Timeline.Item color="#108ee9">
<p></p>
<p></p>
<p></p>
</Timeline.Item>
</Timeline>
</Card>
</div>
</Col>
<Col className="gutter-row" md={8}>
<div className="gutter-box">
<Card bordered={false}>
<div className="pb-m">
<h3></h3>
</div>
<span className="card-tool">
<SyncOutlined />
</span>
<ul className="list-group no-border">
<li className="list-group-item">
<span className="pull-left w-40 mr-m">
<img
src={b1}
className="img-responsive img-circle"
alt="test"
/>
</span>
<div className="clear">
<span className="block"></span>
<span className="text-muted"></span>
</div>
</li>
<li className="list-group-item">
<span className="pull-left w-40 mr-m">
<img
src={b1}
className="img-responsive img-circle"
alt="test"
/>
</span>
<div className="clear">
<span className="block"></span>
<span className="text-muted">~~</span>
</div>
</li>
<li className="list-group-item">
<span className="pull-left w-40 mr-m">
<img
src={b1}
className="img-responsive img-circle"
alt="test"
/>
</span>
<div className="clear">
<span className="block"></span>
<span className="text-muted"></span>
</div>
</li>
<li className="list-group-item">
<span className="pull-left w-40 mr-m">
<img
src={b1}
className="img-responsive img-circle"
alt="test"
/>
</span>
<div className="clear">
<span className="block"></span>
<span className="text-muted">
..
</span>
</div>
</li>
</ul>
</Card>
</div>
</Col>
<Col className="gutter-row" md={8}>
<div className="gutter-box">
<Card bordered={false}>
<div className="pb-m">
<h3>访</h3>
<small>7访</small>
</div>
<span className="card-tool">
<SyncOutlined type="sync" />
</span>
<EchartsViews />
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default Dashboard;

View File

@@ -0,0 +1,116 @@
/**
* Created by hao.cheng on 2017/5/5.
*/
import React from 'react';
import ReactEcharts from 'echarts-for-react';
let xAxisData = [];
let data = [];
for (let i = 0; i < 50; i++) {
xAxisData.push(i);
data.push(Math.ceil((Math.cos(i / 5) * (i / 5) + i / 6) * 5) + 10);
}
const option = {
title: {
text: '最近50天每天项目完成情况',
left: 'center',
textStyle: {
color: '#ccc',
fontSize: 10
}
},
backgroundColor: '#08263a',
xAxis: [{
show: true,
data: xAxisData,
axisLabel: {
textStyle: {
color: '#ccc'
}
}
}, {
show: false,
data: xAxisData
}],
tooltip: {},
visualMap: {
show: false,
min: 0,
max: 50,
dimension: 0,
inRange: {
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
}
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#ccc'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#08263f'
}
},
axisTick: {
show: false
}
},
series: [
{
name: 'Simulate Shadow',
type: 'line',
data: data,
z: 2,
showSymbol: false,
animationDelay: 0,
animationEasing: 'linear',
animationDuration: 1200,
lineStyle: {
normal: {
color: 'transparent'
}
},
areaStyle: {
normal: {
color: '#08263a',
shadowBlur: 50,
shadowColor: '#000'
}
}
}, {
name: '完成项目数',
type: 'bar',
data: data,
xAxisIndex: 1,
z: 3,
itemStyle: {
normal: {
barBorderRadius: 5
}
}
}],
animationEasing: 'elasticOut',
animationEasingUpdate: 'elasticOut',
animationDelay: function (idx: number) {
return idx * 20;
},
animationDelayUpdate: function (idx: number) {
return idx * 20;
}
};
const EchartsProjects = () => (
<ReactEcharts
option={option}
style={{height: '212px', width: '100%'}}
className={'react_for_echarts'}
/>
);
export default EchartsProjects;

View File

@@ -0,0 +1,121 @@
/**
* Created by hao.cheng on 2017/5/5.
*/
import React from 'react';
import ReactEcharts from 'echarts-for-react';
import echarts from 'echarts';
const option = {
title: {
text: '最近7天用户访问量',
left: '50%',
show: false,
textAlign: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#ddd'
}
},
backgroundColor: 'rgba(255,255,255,1)',
padding: [5, 10],
textStyle: {
color: '#7588E4',
},
extraCssText: 'box-shadow: 0 0 5px rgba(0,0,0,0.3)'
},
legend: {
right: 20,
orient: 'vertical',
},
xAxis: {
type: 'category',
data: ['2017-05-01', '2017-05-02', '2017-05-03', '2017-05-04', '2017-05-05', '2017-05-06','2017-05-07'],
boundaryGap: false,
splitLine: {
show: true,
interval: 'auto',
lineStyle: {
color: ['#D4DFF5']
}
},
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#609ee9'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 10
}
}
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
color: ['#D4DFF5']
}
},
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#609ee9'
}
},
axisLabel: {
margin: 0,
textStyle: {
fontSize: 8
}
}
},
series: [{
name: '昨日',
type: 'line',
smooth: true,
showSymbol: false,
symbol: 'circle',
symbolSize: 6,
data: ['1200', '1400', '808', '811', '626', '488', '1600'],
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(216, 244, 247,1)'
}, {
offset: 1,
color: 'rgba(216, 244, 247,1)'
}], false)
}
},
itemStyle: {
normal: {
color: '#58c8da'
}
},
lineStyle: {
normal: {
width: 3
}
}
}]
};
const EchartsViews = () => (
<ReactEcharts
option={option}
style={{height: '350px', width: '100%'}}
className={'react_for_echarts'}
/>
);
export default EchartsViews;

37
src/components/env/index.tsx vendored Normal file
View File

@@ -0,0 +1,37 @@
/*
* File: index.tsx
* Desc: 环境配置
* File Created: 2020-08-02 23:00:28
* Author: yezi
* ------
* Copyright 2020 - present, yezi
*/
import React from 'react';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { Row, Col, Card, Descriptions } from 'antd';
const getEnvs = () => Object.keys(process.env).filter((key) => /^REACT_ADMIN_/i.test(key));
const Env = () => {
const envs = getEnvs();
console.log(process.env);
return (
<div>
<BreadcrumbCustom breads={['环境变量配置']} />
<Row gutter={16}>
<Col md={24}>
<Card title="环境变量配置" bordered={false}>
<Descriptions>
{envs.map((env) => (
<Descriptions.Item key={env} label={env}>
{process.env[env]}
</Descriptions.Item>
))}
</Descriptions>
</Card>
</Col>
</Row>
</div>
);
};
export default Env;

View File

@@ -0,0 +1,29 @@
/*
* File: MultipleMenu.tsx
* Desc: 多级菜单组件
* File Created: 2019-12-18 23:15:35
* Author: chenghao
* ------
* Copyright 2019 - present, karakal
*/
import React from 'react';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { Row, Col, Card } from 'antd';
const MultipleMenu = () => {
return (
<div>
<BreadcrumbCustom breads={['多级菜单']} />
<Row gutter={16}>
<Col md={24}>
<Card title="多级菜单" bordered={false}>
<div></div>
<div></div>
</Card>
</Col>
</Row>
</div>
);
};
export default MultipleMenu;

View File

@@ -0,0 +1,43 @@
/*
* File: QueryParams.js
* Desc: query参数demo
* File Created: 2018-11-25 23:18:09
* Author: chenghao
* Copyright 2018 - present, chenghao
*/
import React, { Component } from 'react';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
type QueryParamsProps = {
query: any;
};
class QueryParams extends Component<QueryParamsProps> {
render() {
const { query } = this.props;
return (
<div>
<BreadcrumbCustom breads={['queryParams']} />
<Row gutter={16}>
<Col md={24}>
<Card title="query参数Demo" bordered={false}>
<div>1 {query.param1}</div>
<div>2 {query.param2}</div>
<div>
{' '}
{query.others || (
<a href="#/app/extension/queryParams?others=nothing">
</a>
)}
</div>
</Card>
</Col>
</Row>
</div>
);
}
}
export default QueryParams;

View File

@@ -0,0 +1,37 @@
/*
* File: Visitor.tsx
* Desc: 访客
* File Created: 2019-10-25 22:31:37
* Author: chenghao
* ------
* Copyright 2019 - present, chenghao
*/
import React from 'react';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { Row, Col, Card } from 'antd';
const Visitor = () => {
return (
<>
<BreadcrumbCustom breads={['visitor']} />
<Row gutter={16}>
<Col md={24}>
<Card
title="访客模式"
bordered={false}
bodyStyle={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: 500,
}}
>
访访
</Card>
</Col>
</Row>
</>
);
};
export default Visitor;

70
src/components/index.tsx Normal file
View File

@@ -0,0 +1,70 @@
/**
* 路由组件出口文件
* yezi 2018年6月24日
*/
import Loadable from 'react-loadable';
import Loading from './widget/Loading';
import BasicTable from './tables/BasicTables';
import AdvancedTable from './tables/AdvancedTables';
import AsynchronousTable from './tables/AsynchronousTable';
import Echarts from './charts/Echarts';
import Recharts from './charts/Recharts';
import Icons from './ui/Icons';
import Buttons from './ui/Buttons';
import Spins from './ui/Spins';
import Modals from './ui/Modals';
import Notifications from './ui/Notifications';
import Tabs from './ui/Tabs';
import Banners from './ui/banners';
import Drags from './ui/Draggable';
import Dashboard from './dashboard/Dashboard';
import Gallery from './ui/Gallery';
import BasicAnimations from './animation/BasicAnimations';
import ExampleAnimations from './animation/ExampleAnimations';
import AuthBasic from './auth/Basic';
import RouterEnter from './auth/RouterEnter';
import Cssmodule from './cssmodule';
import MapUi from './ui/map';
import QueryParams from './extension/QueryParams';
import Visitor from './extension/Visitor';
import MultipleMenu from './extension/MultipleMenu';
import Sub1 from './smenu/Sub1';
import Sub2 from './smenu/Sub2';
import Env from './env';
const WysiwygBundle = Loadable({
// 按需加载富文本配置
loader: () => import('./ui/Wysiwyg'),
loading: Loading,
});
export default {
BasicTable,
AdvancedTable,
AsynchronousTable,
Echarts,
Recharts,
Icons,
Buttons,
Spins,
Modals,
Notifications,
Tabs,
Banners,
Drags,
Dashboard,
Gallery,
BasicAnimations,
ExampleAnimations,
AuthBasic,
RouterEnter,
WysiwygBundle,
Cssmodule,
MapUi,
QueryParams,
Visitor,
MultipleMenu,
Sub1,
Sub2,
Env,
} as any;

View File

@@ -0,0 +1,113 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React, { useEffect } from 'react';
import { Button, Form, Input } from 'antd';
import { PwaInstaller } from '../widget';
import { useAlita } from 'redux-alita';
import { RouteComponentProps } from 'react-router';
import { FormProps } from 'antd/lib/form';
import umbrella from 'umbrella-storage';
import { GithubOutlined, LockOutlined, UserOutlined } from '@ant-design/icons';
import { useUpdateEffect } from 'ahooks';
import { trailwayLogin } from '../../service';
const FormItem = Form.Item;
type LoginProps = {
setAlitaState: (param: any) => void;
auth: any;
} & RouteComponentProps &
FormProps;
const Login = (props: LoginProps) => {
const { history } = props;
const [auth, setAlita] = useAlita({ auth: {} }, { light: true });
useEffect(() => {
setAlita('auth', null);
}, [setAlita]);
useUpdateEffect(() => {
if (auth && auth.uid) {
// 判断是否登陆
umbrella.setLocalStorage('user', auth);
history.push('/');
}
}, [history, auth]);
const handleSubmit = (values: any) => {
if (checkUser(values)) {
setAlita({ funcName: values.userName, stateName: 'auth' });
}
};
const checkUser = (values: any) => {
// const users = [
// ['admin', 'admin'],
// ['guest', 'guest'],
// ];
// return users.some((user) => user[0] === values.userName && user[1] === values.password);
let success = trailwayLogin({ username: values.userName, password: values.password }).then(
(res) => {
console.log(res);
return res.data.success === true;
}
);
console.log(success);
return success;
};
const gitHub = () => {
window.location.href =
'https://github.com/login/oauth/authorize?client_id=792cdcd244e98dcd2dee&redirect_uri=http://localhost:3006/&scope=user&state=reactAdmin';
};
return (
<div className="login">
<div className="login-form">
<div className="login-logo">
<span>React Admin</span>
<PwaInstaller />
</div>
<Form onFinish={handleSubmit} style={{ maxWidth: '300px' }}>
<FormItem
name="userName"
rules={[{ required: true, message: '请输入用户名!' }]}
>
<Input
prefix={<UserOutlined size={13} />}
placeholder="管理员输入admin, 游客输入guest"
/>
</FormItem>
<FormItem name="password" rules={[{ required: true, message: '请输入密码!' }]}>
<Input
prefix={<LockOutlined size={13} />}
type="password"
placeholder="管理员输入admin, 游客输入guest"
/>
</FormItem>
<FormItem>
<span className="login-form-forgot" style={{ float: 'right' }}>
</span>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
style={{ width: '100%' }}
>
</Button>
<p style={{ display: 'flex', justifyContent: 'space-between' }}>
<span> !</span>
<span onClick={gitHub}>
<GithubOutlined />
()
</span>
</p>
</FormItem>
</Form>
</div>
</div>
);
};
export default Login;

View File

@@ -0,0 +1,31 @@
/**
* Created by hao.cheng on 2017/5/7.
*/
import React from 'react';
import img from '../../style/imgs/404.png';
class NotFound extends React.Component {
state = {
animated: '',
};
enter = () => {
this.setState({ animated: 'hinge' });
};
render() {
return (
<div
className="center"
style={{ height: '100%', background: '#ececec', overflow: 'hidden' }}
>
<img
src={img}
alt="404"
className={`animated swing ${this.state.animated}`}
onMouseEnter={this.enter}
/>
</div>
);
}
}
export default NotFound;

View File

@@ -0,0 +1,28 @@
/*
* File: Sub1.tsx
* Desc: 异步子菜单
* File Created: 2020-01-21 11:31:15
* Author: chenghao at <865470087@qq.com>
* ------
* Copyright 2020 - present, chenghao
*/
import React from 'react';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { Row, Col, Card } from 'antd';
const SmenuSub1 = () => {
return (
<div>
<BreadcrumbCustom breads={['异步菜单']} />
<Row gutter={16}>
<Col md={24}>
<Card title="异步子菜单" bordered={false}>
<div>1</div>
</Card>
</Col>
</Row>
</div>
);
};
export default SmenuSub1;

View File

@@ -0,0 +1,28 @@
/*
* File: Sub2.tsx
* Desc: 异步子菜单
* File Created: 2020-01-21 11:31:15
* Author: chenghao at <865470087@qq.com>
* ------
* Copyright 2020 - present, chenghao
*/
import React from 'react';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { Row, Col, Card } from 'antd';
const SmenuSub2 = () => {
return (
<div>
<BreadcrumbCustom breads={['异步菜单']} />
<Row gutter={16}>
<Col md={24}>
<Card title="异步子菜单" bordered={false}>
<div>2</div>
</Card>
</Col>
</Row>
</div>
);
};
export default SmenuSub2;

View File

@@ -0,0 +1,45 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import FixedTable from './FixedTable';
import ExpandedTable from './ExpandedTable';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
class AdvancedTables extends React.Component {
render() {
return (
<div className="gutter-example">
<BreadcrumbCustom breads={['表格', '高级表格']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="固定列" bordered={false}>
<FixedTable />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="可展开" bordered={false}>
<ExpandedTable />
</Card>
</div>
</Col>
{/* <Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="可编辑" bordered={false}>
<EditableTable />
</Card>
</div>
</Col> */}
</Row>
</div>
);
}
}
export default AdvancedTables;

View File

@@ -0,0 +1,102 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React from 'react';
import { Table, Button, Row, Col, Card } from 'antd';
import { getBbcNews } from '../../service';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
const columns = [
{
title: '新闻标题',
dataIndex: 'title',
width: 100,
render: (text: any, record: any) => (
<a href={record.url} target="_blank" rel="noopener noreferrer">
{text}
</a>
),
},
{
title: '作者',
dataIndex: 'author',
width: 80,
},
{
title: '发布时间',
dataIndex: 'publishedAt',
width: 80,
},
{
title: '描述',
dataIndex: 'description',
width: 200,
},
];
class AsynchronousTable extends React.Component {
state = {
selectedRowKeys: [], // Check here to configure the default column
loading: false,
data: [],
};
componentDidMount() {
this.start();
}
start = () => {
this.setState({ loading: true });
getBbcNews().then(({ articles }: { articles: any }) => {
this.setState({
data: articles,
loading: false,
});
});
};
onSelectChange = (selectedRowKeys: string[]) => {
console.log('selectedRowKeys changed: ', selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { loading, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
};
const hasSelected = selectedRowKeys.length > 0;
return (
<div className="gutter-example">
<BreadcrumbCustom breads={['表格', '异步表格']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="异步表格--BBC新闻今日热门" bordered={false}>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
onClick={this.start}
disabled={loading}
loading={loading}
>
Reload
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected
? `Selected ${selectedRowKeys.length} items`
: ''}
</span>
</div>
<Table
rowSelection={rowSelection as any}
columns={columns}
dataSource={this.state.data}
/>
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default AsynchronousTable;

View File

@@ -0,0 +1,65 @@
/**
* Created by hao.cheng on 2017/4/15.
*/
import React from 'react';
import { Table, Button } from 'antd';
import { DownOutlined } from '@ant-design/icons';
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text: any) => <span>{text}</span>,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
{
title: 'Action',
key: 'action',
render: (text: any, record: any) => (
<span>
<Button>Action {record.name}</Button>
<span className="ant-divider" />
<Button>Delete</Button>
<span className="ant-divider" />
<Button className="ant-dropdown-link">
More actions <DownOutlined />
</Button>
</span>
),
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
];
const BasicTable = () => <Table columns={columns} dataSource={data} />;
export default BasicTable;

View File

@@ -0,0 +1,52 @@
/**
* Created by hao.cheng on 2017/4/15.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import BasicTable from './BasicTable';
import SelectTable from './SelectTable';
import SortTable from './SortTable';
import SearchTable from './SearchTable';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
const BasicTables = () => (
<div className="gutter-example">
<BreadcrumbCustom breads={['表格', '基础表格']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="基础表格" bordered={false}>
<BasicTable />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="基础表格" bordered={false}>
<SelectTable />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="可控的筛选和排序" bordered={false}>
<SortTable />
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="自定义筛选" bordered={false}>
<SearchTable />
</Card>
</div>
</Col>
</Row>
</div>
);
export default BasicTables;

View File

@@ -0,0 +1,28 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React from 'react';
import { Table, Button } from 'antd';
const columns = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Age', dataIndex: 'age', key: 'age' },
{ title: 'Address', dataIndex: 'address', key: 'address' },
{ title: 'Action', dataIndex: '', key: 'x', render: () => <Button>Delete</Button> },
];
const data = [
{ key: 1, name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.' },
{ key: 2, name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.' },
{ key: 3, name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.' },
];
const ExpandedTable = () => (
<Table
columns={columns}
expandedRowRender={record => <p>{record.description}</p>}
dataSource={data}
/>
);
export default ExpandedTable;

View File

@@ -0,0 +1,45 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React from 'react';
import { Table } from 'antd';
import { ColumnProps } from 'antd/lib/table';
const columns: ColumnProps<any>[] = [
{ title: 'Full Name', width: 100, dataIndex: 'name', key: 'name', fixed: 'left' },
{ title: 'Age', width: 100, dataIndex: 'age', key: 'age', fixed: 'left' },
{ title: 'Column 1', dataIndex: 'address', key: '1' },
{ title: 'Column 2', dataIndex: 'address', key: '2' },
{ title: 'Column 3', dataIndex: 'address', key: '3' },
{ title: 'Column 4', dataIndex: 'address', key: '4' },
{ title: 'Column 5', dataIndex: 'address', key: '5' },
{ title: 'Column 6', dataIndex: 'address', key: '6' },
{ title: 'Column 7', dataIndex: 'address', key: '7' },
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{
title: 'Action',
key: 'operation',
fixed: 'right',
width: 100,
render: () => <span>action</span>,
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York Park',
},
{
key: '2',
name: 'Jim Green',
age: 40,
address: 'London Park',
},
];
const FixedTable = () => <Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />;
export default FixedTable;

View File

@@ -0,0 +1,150 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import React from 'react';
import { Table, Input, Button } from 'antd';
import { SmileOutlined } from '@ant-design/icons';
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Joe Black',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Jim Green',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
class SearchTable extends React.Component {
state = {
filterDropdownVisible: false,
data,
searchText: '',
filtered: false,
};
searchInput: any;
onInputChange = (e: any) => {
this.setState({ searchText: e.target.value });
};
onSearch = () => {
const { searchText } = this.state;
const reg = new RegExp(searchText, 'gi');
this.setState({
filterDropdownVisible: false,
filtered: !!searchText,
data: data
.map((record) => {
const match = record.name.match(reg);
if (!match) {
return null;
}
return {
...record,
name: (
<span>
{record.name
.split(reg)
.map((text, i) =>
i > 0
? [<span className="highlight">{match[0]}</span>, text]
: text
)}
</span>
),
};
})
.filter((record) => !!record),
});
};
render() {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
filterDropdown: (
<div className="custom-filter-dropdown">
<Input
ref={(ele) => (this.searchInput = ele)}
placeholder="Search name"
value={this.state.searchText}
onChange={this.onInputChange}
onPressEnter={this.onSearch}
/>
<Button type="primary" onClick={this.onSearch}>
Search
</Button>
</div>
),
filterIcon: (
<SmileOutlined style={{ color: this.state.filtered ? '#108ee9' : '#aaa' }} />
),
filterDropdownVisible: this.state.filterDropdownVisible,
onFilterDropdownVisibleChange: (visible: boolean) =>
this.setState({ filterDropdownVisible: visible }, () =>
this.searchInput.focus()
),
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
filters: [
{
text: 'London',
value: 'London',
},
{
text: 'New York',
value: 'New York',
},
],
onFilter: (value: any, record: any) => record.address.indexOf(value) === 0,
},
];
return (
<div>
<Table columns={columns} dataSource={this.state.data} />
<style>{`
.custom-filter-dropdown {
padding: 8px;
border-radius: 6px;
background: #fff;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
}
.custom-filter-dropdown input {
width: 130px;
margin-right: 8px;
}
.highlight {
color: #f50;
}
`}</style>
</div>
);
}
}
export default SearchTable;

View File

@@ -0,0 +1,82 @@
/**
* Created by hao.cheng on 2017/4/15.
*/
import React from 'react';
import { Table } from 'antd';
// import { TableRowSelection } from 'antd/lib/table';
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
];
const data: any[] = [];
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`,
});
}
class SelectTable extends React.Component {
state = {
selectedRowKeys: [], // Check here to configure the default column
};
onSelectChange = (selectedRowKeys: string[] | number[]) => {
console.log('selectedRowKeys changed: ', selectedRowKeys);
this.setState({ selectedRowKeys });
};
render() {
const { selectedRowKeys } = this.state;
const rowSelection: any = {
selectedRowKeys,
onChange: this.onSelectChange,
selections: [
{
key: 'odd',
text: '选择奇数列',
onSelect: (changableRowKeys: string[]) => {
let newSelectedRowKeys = [];
newSelectedRowKeys = changableRowKeys.filter((key, index) => {
if (index % 2 !== 0) {
return false;
}
return true;
});
this.setState({ selectedRowKeys: newSelectedRowKeys });
},
},
{
key: 'even',
text: '选择偶数列',
onSelect: (changableRowKeys: string[]) => {
let newSelectedRowKeys = [];
newSelectedRowKeys = changableRowKeys.filter((key, index) => {
if (index % 2 !== 0) {
return true;
}
return false;
});
this.setState({ selectedRowKeys: newSelectedRowKeys });
},
},
],
// onSelection: this.onSelection,
};
return <Table rowSelection={rowSelection} columns={columns} dataSource={data} />;
}
}
export default SelectTable;

View File

@@ -0,0 +1,117 @@
/**
* Created by hao.cheng on 2017/4/15.
*/
import React from 'react';
import { Table, Button } from 'antd';
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
type SortTableState = {
filteredInfo: any;
sortedInfo: any;
};
class SortTable extends React.Component<any, SortTableState> {
constructor(props: any) {
super(props);
this.state = {
filteredInfo: {},
sortedInfo: {},
};
}
handleChange = (pagination: any, filters: any, sorter: any) => {
console.log('Various parameters', pagination, filters, sorter);
this.setState({
filteredInfo: filters,
sortedInfo: sorter,
});
};
clearFilters = () => {
this.setState({ filteredInfo: null });
};
clearAll = () => {
this.setState({
filteredInfo: null,
sortedInfo: null,
});
};
setAgeSort = () => {
this.setState({
sortedInfo: {
order: 'descend',
columnKey: 'age',
},
});
};
render() {
let { sortedInfo, filteredInfo } = this.state;
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
filters: [{ text: 'Joe', value: 'Joe' }, { text: 'Jim', value: 'Jim' }],
filteredValue: filteredInfo.name || null,
onFilter: (value: any, record: any) => record.name.includes(value),
sorter: (a: any, b: any) => a.name.length - b.name.length,
sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
sorter: (a: any, b: any) => a.age - b.age,
sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order,
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
filters: [
{ text: 'London', value: 'London' },
{ text: 'New York', value: 'New York' },
],
filteredValue: filteredInfo.address || null,
onFilter: (value: any, record: any) => record.address.includes(value),
sorter: (a: any, b: any) => a.address.length - b.address.length,
sortOrder: sortedInfo.columnKey === 'address' && sortedInfo.order,
},
];
return (
<div>
<div className="table-operations">
<Button onClick={this.setAgeSort}>Sort age</Button>
<Button onClick={this.clearFilters}>Clear filters</Button>
<Button onClick={this.clearAll}>Clear filters and sorters</Button>
</div>
<Table columns={columns} dataSource={data} onChange={this.handleChange} />
</div>
);
}
}
export default SortTable;

View File

@@ -0,0 +1,167 @@
/**
* Created by hao.cheng on 2017/4/23.
*/
import React from 'react';
import { Row, Col, Card, Button, Radio, Menu, Dropdown } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { RadioChangeEvent } from 'antd/lib/radio';
import { ButtonSize } from 'antd/lib/button';
import { DownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
type ButtonsState = {
size: ButtonSize;
loading: boolean;
iconLoading: boolean;
};
class Buttons extends React.Component<any, ButtonsState> {
constructor(props: any) {
super(props);
this.state = {
size: 'middle',
loading: false,
iconLoading: false,
};
}
handleSizeChange = (e: RadioChangeEvent) => {
this.setState({ size: e.target.value });
};
handleMenuClick = (e: any) => {
console.log('click', e);
};
enterLoading = () => {
this.setState({ loading: true });
};
enterIconLoading = () => {
this.setState({ iconLoading: true });
};
render() {
const size = this.state.size;
const menu = (
<Menu onClick={this.handleMenuClick}>
<Menu.Item key="1">1st item</Menu.Item>
<Menu.Item key="2">2nd item</Menu.Item>
<Menu.Item key="3">3rd item</Menu.Item>
</Menu>
);
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '按钮']} />
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary">Primary</Button>
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
<Button danger>Danger</Button>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary" shape="circle" icon="search" />
<Button type="primary" icon="search">
Search
</Button>
<Button shape="circle" icon="search" />
<Button icon="search">Search</Button>
<br />
<Button shape="circle" icon="search" />
<Button icon="search">Search</Button>
<Button type="dashed" shape="circle" icon="search" />
<Button type="dashed" icon="search">
Search
</Button>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Radio.Group value={size} onChange={this.handleSizeChange}>
<Radio.Button value="large">Large</Radio.Button>
<Radio.Button value="middle">Middle</Radio.Button>
<Radio.Button value="small">Small</Radio.Button>
</Radio.Group>
<br />
<br />
<Button type="primary" shape="circle" icon="download" size={size} />
<Button type="primary" icon="download" size={size}>
Download
</Button>
<Button type="primary" size={size}>
Normal
</Button>
<br />
<Button.Group size={size}>
<Button type="primary">
<LeftOutlined />
Backward
</Button>
<Button type="primary">
Forward
<RightOutlined />
</Button>
</Button.Group>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary">primary</Button>
<Button>secondary</Button>
<Dropdown overlay={menu}>
<Button>
more <DownOutlined />
</Button>
</Dropdown>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary" loading>
Loading
</Button>
<Button type="primary" size="small" loading>
Loading
</Button>
<br />
<Button
type="primary"
loading={this.state.loading}
onClick={this.enterLoading}
>
Click me!
</Button>
<Button
type="primary"
icon="poweroff"
loading={this.state.iconLoading}
onClick={this.enterIconLoading}
>
Click me!
</Button>
<br />
<Button shape="circle" loading />
<Button type="primary" shape="circle" loading />
</Card>
</div>
</Col>
</Row>
<style>{`
.button-demo .ant-btn {
margin-right: 8px;
margin-bottom: 12px;
}
`}</style>
</div>
);
}
}
export default Buttons;

View File

@@ -0,0 +1,132 @@
/**
* Created by hao.cheng on 2017/4/28.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import Draggable from 'react-draggable';
class Drags extends React.Component {
state = {
activeDrags: 0,
deltaPosition: {
x: 0,
y: 0,
},
controlledPosition: {
x: -400,
y: 200,
},
};
onStart = () => {
let { activeDrags } = this.state;
this.setState({ activeDrags: ++activeDrags });
};
onStop = () => {
let { activeDrags } = this.state;
this.setState({ activeDrags: --activeDrags });
};
handleDrag = (e: any, ui: any) => {
const { x, y } = this.state.deltaPosition;
this.setState({
deltaPosition: {
x: x + ui.deltaX,
y: y + ui.deltaY,
},
});
};
render() {
const dragHandlers = { onStart: this.onStart, onStop: this.onStop };
const { deltaPosition } = this.state;
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '拖拽']} />
<Row gutter={16}>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable {...dragHandlers}>
<Card bordered={false} className={'dragDemo'}>
I can be dragged anywhere
</Card>
</Draggable>
</div>
</Col>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable axis="x" {...dragHandlers}>
<Card bordered={false} className={'dragDemo'}>
I can only be dragged horizonally (x axis)
</Card>
</Draggable>
</div>
</Col>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable axis="y" {...dragHandlers}>
<Card bordered={false} className={'dragDemo'}>
I can only be dragged vertically (y axis)
</Card>
</Draggable>
</div>
</Col>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable onDrag={this.handleDrag} {...dragHandlers}>
<Card bordered={false} className={'dragDemo'}>
<div>I track my deltas</div>
<div>
x: {deltaPosition.x.toFixed(0)}, y:{' '}
{deltaPosition.y.toFixed(0)}
</div>
</Card>
</Draggable>
</div>
</Col>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable handle="strong" {...dragHandlers}>
<Card bordered={false} className={'dragDemo no-cursor'}>
<strong className="cursor-move">
<div>Drag here</div>
</strong>
<div>You must click my handle to drag me</div>
</Card>
</Draggable>
</div>
</Col>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable cancel="strong" {...dragHandlers}>
<Card bordered={false} className={'dragDemo'}>
<strong className="no-cursor">
<div>Can't drag here</div>
</strong>
<div>Dragging here works</div>
</Card>
</Draggable>
</div>
</Col>
<Col className="gutter-row" md={6}>
<div className="gutter-box">
<Draggable
bounds={{ top: -100, left: -100, right: 100, bottom: 100 }}
{...dragHandlers}
>
<Card bordered={false} className={'dragDemo'}>
<div>I can only be moved 100px in any direction.</div>
</Card>
</Draggable>
</div>
</Col>
</Row>
<style>{`
.dragDemo {
height: 180px;
}
`}</style>
</div>
);
}
}
export default Drags;

View File

@@ -0,0 +1,223 @@
/**
* Created by hao.cheng on 2017/5/6.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import PhotoSwipe from 'photoswipe';
import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
import 'photoswipe/dist/photoswipe.css';
import 'photoswipe/dist/default-skin/default-skin.css';
class Gallery extends React.Component {
state = {
gallery: null,
};
componentDidMount() {}
componentWillUnmount = () => {
this.closeGallery();
};
pswpElement: any;
gallery: any;
openGallery = (item: any) => {
const items = [
{
src: item,
w: 0,
h: 0,
},
];
const pswpElement = this.pswpElement;
const options = { index: 0 };
this.gallery = new PhotoSwipe(pswpElement, PhotoswipeUIDefault, items, options);
this.gallery.listen('gettingData', (index: number, item: any) => {
const _this = this;
if (item.w < 1 || item.h < 1) {
// unknown size
var img = new Image();
img.onload = function () {
// will get size after load
item.w = (this as any).width; // set image width
item.h = (this as any).height; // set image height
_this.gallery.invalidateCurrItems(); // reinit Items
_this.gallery.updateSize(true); // reinit Items
};
img.src = item.src; // let's download image
}
});
this.gallery.init();
};
closeGallery = () => {
if (!this.gallery) return;
this.gallery.close();
};
render() {
const imgs = [
[
'http://img.hb.aicdn.com/1cad414972c5db2b8c1942289e3aeef37175006a8bb16-CBtjtX_fw',
'http://img.hb.aicdn.com/016f2e13934397e17c3482a4529f3da1149d37fd2a99c-RVM1Gi_fw',
'http://img.hb.aicdn.com/8c5d5f2bf6427d1b5ed8657a7ae0c9938d3465e367899-AJ0zVA_fw',
'http://img.hb.aicdn.com/bd71ccac0b16bbcade255a1a8a63504d71c7dee9a8652-zBCN9d_fw',
'http://img.hb.aicdn.com/37a40cb04345463858d45418ae6ed9ef319e30dc37a45-o4pQ0j_fw',
],
[
'http://img.hb.aicdn.com/5fad6c3a14a9b80c4448835bb6b23ab895d18e234eff3-BPGmox_fw',
'http://img.hb.aicdn.com/a1a19de5dac212a646ba6967ef565786399fb1665bd04-EEvwzR_fw',
'http://img.hb.aicdn.com/06595f8044e881de3a82d691768bc8c21a2a9f3633d60-XKjC2s_fw',
'http://img.hb.aicdn.com/880787b36d45efbe05aa409c867db29a3028e02da7f9b-qxGib9_fw',
'http://img.hb.aicdn.com/4964b97f6f6eb61a20922b40842adf0169c44e491c4b60-azX1S7_fw',
],
[
'http://img.hb.aicdn.com/ff97d00944edfc706c62dd5c0e955c4099a37b407534f-BcUqf0_fw',
'http://img.hb.aicdn.com/0e22be22b08c6f78b94283b6cfa890093ac3cae8401e7-b1ftfi_fw',
'http://img.hb.aicdn.com/879f870e15f7cc0847c8ae19a5fcbe974d5904bb181d7-RGmtNU_fw',
'http://img.hb.aicdn.com/b4a8e62958555a97dc3de9ccb03284bf556c042925522-x50qGv_fw',
'http://img.hb.aicdn.com/1ef493a15674e9fd523b248ea4ec43d2ea9ce6952ff3e-WavWKc_fw',
],
[
'http://img.hb.aicdn.com/8e16efec78ac4a3684fc8999d18e3661af40fd4510a25-DDvQON_fw',
'http://img.hb.aicdn.com/61dfa024c8040e6a5bcb03d42928fbcb0c87c1a54e731-yc4lvV_fw',
'http://img.hb.aicdn.com/6783b4d7811ad7fb87b1446c5488b91179f7608118289-hpEyP3_fw',
'http://img.hb.aicdn.com/7be61ba6bdb20a73be63edc387b16eec72d0bbb51c7ef-XafA07_fw',
'http://img.hb.aicdn.com/bd3ba3f907fe098b911947e0020615b50fc340ed2df72-WsuHuM_fw',
],
[
'http://img.hb.aicdn.com/71471aaac95eade66400a390863b37c76d9addcd14982-0H6sak_fw',
'http://img.hb.aicdn.com/cb16c68c4d3b7a08b5e91cd351f6b723634ca3fc27d4d-m1JD8z_fw',
'http://img.hb.aicdn.com/e3559b6e8d7237857382050e5659a64cc0b7d696a2869-stcRXA_fw',
'http://img.hb.aicdn.com/4ea229436fcf2077502953907a6afb16d3c5cd611b8e2-0dVIeH_fw',
'http://img.hb.aicdn.com/98c786f4314736f95a42bf927bf65a82d305a532c6258-njI6id_fw',
],
];
const imgsTag = imgs.map((v1) =>
v1.map((v2) => (
<div className="gutter-box" key={v2}>
<Card bordered={false} bodyStyle={{ padding: 0 }}>
<div>
<img
onClick={() => this.openGallery(v2)}
alt="example"
width="100%"
src={v2}
/>
</div>
<div className="pa-m">
<h3>React Admin</h3>
<small>
<a
href="https://yezihaohao.github.io/"
target="_blank"
rel="noopener noreferrer"
>
https://yezihaohao.github.io/
</a>
</small>
</div>
</Card>
</div>
))
);
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom
breads={['UI', '画廊(图片来自花瓣网,仅学习,若侵权请联系删除)']}
/>
<Row gutter={10}>
<Col className="gutter-row" md={5}>
{imgsTag[0]}
</Col>
<Col className="gutter-row" md={5}>
{imgsTag[1]}
</Col>
<Col className="gutter-row" md={5}>
{imgsTag[2]}
</Col>
<Col className="gutter-row" md={5}>
{imgsTag[3]}
</Col>
<Col className="gutter-row" md={4}>
{imgsTag[4]}
</Col>
</Row>
<div
className="pswp"
tabIndex={-1}
role="dialog"
aria-hidden="true"
ref={(div) => {
this.pswpElement = div;
}}
>
<div className="pswp__bg" />
<div className="pswp__scroll-wrap">
<div className="pswp__container">
<div className="pswp__item" />
<div className="pswp__item" />
<div className="pswp__item" />
</div>
<div className="pswp__ui pswp__ui--hidden">
<div className="pswp__top-bar">
<div className="pswp__counter" />
<button
className="pswp__button pswp__button--close"
title="Close (Esc)"
/>
<button
className="pswp__button pswp__button--share"
title="Share"
/>
<button
className="pswp__button pswp__button--fs"
title="Toggle fullscreen"
/>
<button
className="pswp__button pswp__button--zoom"
title="Zoom in/out"
/>
<div className="pswp__preloader">
<div className="pswp__preloader__icn">
<div className="pswp__preloader__cut">
<div className="pswp__preloader__donut" />
</div>
</div>
</div>
</div>
<div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div className="pswp__share-tooltip" />
</div>
<button
className="pswp__button pswp__button--arrow--left"
title="Previous (arrow left)"
/>
<button
className="pswp__button pswp__button--arrow--right"
title="Next (arrow right)"
/>
<div className="pswp__caption">
<div className="pswp__caption__center" />
</div>
</div>
</div>
</div>
<style>{`
.ant-card-body img {
cursor: pointer;
}
`}</style>
</div>
);
}
}
export default Gallery;

309
src/components/ui/Icons.tsx Normal file
View File

@@ -0,0 +1,309 @@
/**
* Created by hao.cheng on 2017/4/22.
*/
import React from 'react';
import Emoji from './emoji';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
const emojiList = () => {
let _elements = [];
for (let i = 1; i < 30; i++) {
_elements.push(
<li key={i}>
<Emoji type={'emoji-' + i} />
<span>{'emoji-' + i}</span>
</li>
);
}
return _elements;
};
const icons = {
direction: [
'step-backward',
'step-forward',
'fast-backward',
'fast-forward',
'shrink',
'arrows-alt',
'down',
'up',
'left',
'right',
'caret-up',
'caret-down',
'caret-left',
'caret-right',
'up-circle',
'down-circle',
'left-circle',
'right-circle',
'up-circle-o',
'down-circle-o',
'right-circle-o',
'left-circle-o',
'double-right',
'double-left',
'verticle-left',
'verticle-right',
'forward',
'backward',
'rollback',
'enter',
'retweet',
'swap',
'swap-left',
'swap-right',
'arrow-up',
'arrow-down',
'arrow-left',
'arrow-right',
'play-circle',
'play-circle-o',
'up-square',
'down-square',
'left-square',
'right-square',
'up-square-o',
'down-square-o',
'left-square-o',
'right-square-o',
'login',
'logout',
'menu-fold',
'menu-unfold',
],
suggestion: [
'question',
'question-circle-o',
'question-circle',
'plus',
'plus-circle-o',
'plus-circle',
'pause',
'pause-circle-o',
'pause-circle',
'minus',
'minus-circle-o',
'minus-circle',
'plus-square',
'plus-square-o',
'minus-square',
'minus-square-o',
'info',
'info-circle-o',
'info-circle',
'exclamation',
'exclamation-circle-o',
'exclamation-circle',
'close',
'close-circle',
'close-circle-o',
'close-square',
'close-square-o',
'check',
'check-circle',
'check-circle-o',
'check-square',
'check-square-o',
'clock-circle-o',
'clock-circle',
],
logo: [
'android',
'android-o',
'apple',
'apple-o',
'windows',
'windows-o',
'ie',
'chrome',
'github',
'aliwangwang',
'aliwangwang-o',
'dingding',
'dingding-o',
],
other: [
'lock',
'unlock',
'area-chart',
'pie-chart',
'bar-chart',
'dot-chart',
'bars',
'book',
'calendar',
'cloud',
'cloud-download',
'code',
'code-o',
'copy',
'credit-card',
'delete',
'desktop',
'download',
'edit',
'ellipsis',
'file',
'file-text',
'file-unknown',
'file-pdf',
'file-excel',
'file-jpg',
'file-ppt',
'file-add',
'folder',
'folder-open',
'folder-add',
'hdd',
'frown',
'frown-o',
'meh',
'meh-o',
'smile',
'smile-o',
'inbox',
'laptop',
'appstore-o',
'appstore',
'line-chart',
'link',
'mail',
'mobile',
'notification',
'paper-clip',
'picture',
'poweroff',
'reload',
'search',
'setting',
'share-alt',
'shopping-cart',
'tablet',
'tag',
'tag-o',
'tags',
'tags-o',
'to-top',
'upload',
'user',
'video-camera',
'home',
'loading',
'loading-3-quarters',
'cloud-upload-o',
'cloud-download-o',
'cloud-upload',
'cloud-o',
'star-o',
'star',
'heart-o',
'heart',
'environment',
'environment-o',
'eye',
'eye-o',
'camera',
'camera-o',
'save',
'team',
'solution',
'phone',
'filter',
'exception',
'export',
'customer-service',
'qrcode',
'scan',
'like',
'like-o',
'dislike',
'dislike-o',
'message',
'pay-circle',
'pay-circle-o',
'calculator',
'pushpin',
'pushpin-o',
'bulb',
'select',
'switcher',
'rocket',
'bell',
'disconnect',
'database',
'compass',
'barcode',
'hourglass',
'key',
'flag',
'layout',
'printer',
'sound',
'usb',
'skin',
'tool',
'sync',
'wifi',
'car',
'schedule',
'user-add',
'user-delete',
'usergroup-add',
'usergroup-delete',
'man',
'woman',
'shop',
'gift',
'idcard',
'medicine-box',
'red-envelope',
'coffee',
'copyright',
'trademark',
'safety',
'wallet',
'bank',
'trophy',
'contacts',
'global',
'shake',
'api',
],
};
const iconsList = Object.keys(icons).map((v) =>
(icons as any)[v].map((icon: any, i: number) => (
<li key={i}>
{/* <Icon type={icon} style={{ fontSize: 15 }} /> */}
<span>{icon}</span>
</li>
))
);
const Icons = () => (
<div className="gutter-example">
<BreadcrumbCustom breads={['UI', '图标']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card bordered={false}>
<ul className="icons-list">{emojiList()}</ul>
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card bordered={false}>
<ul className="icons-list">{iconsList}</ul>
</Card>
</div>
</Col>
</Row>
</div>
);
export default Icons;

View File

@@ -0,0 +1,250 @@
/**
* Created by hao.cheng on 2017/4/23.
*/
import React, { Component } from 'react';
import { Row, Col, Card, Modal, Button } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
const confirm = Modal.confirm;
class S extends Component {
state = {
visible: false,
ModalText2: 'Content of the modal dialog',
visible2: false,
loading3: false,
visible3: false,
modal1Visible: false,
modal2Visible: false,
confirmLoading2: false,
};
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
console.log(e);
this.setState({
visible: false,
});
};
handleCancel = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
this.setState({
visible: false,
});
};
showModal2 = () => {
this.setState({
visible2: true,
});
};
handleOk2 = () => {
this.setState({
ModalText2: 'The modal dialog will be closed after two seconds',
confirmLoading2: true,
});
setTimeout(() => {
this.setState({
visible2: false,
confirmLoading2: false,
});
}, 2000);
};
setModal1Visible = (modal1Visible: boolean) => {
this.setState({ modal1Visible });
};
setModal2Visible = (modal2Visible: boolean) => {
this.setState({ modal2Visible });
};
handleCancel2 = () => {
console.log('Clicked cancel button');
this.setState({
visible2: false,
});
};
showModal3 = () => {
this.setState({
visible3: true,
});
};
handleOk3 = () => {
this.setState({ loading3: true });
setTimeout(() => {
this.setState({ loading3: false, visible3: false });
}, 3000);
};
handleCancel3 = () => {
this.setState({ visible3: false });
};
showConfirm4 = () => {
confirm({
title: 'Want to delete these items?',
content: 'some descriptions',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
info = () => {
Modal.info({
title: 'This is a notification message',
content: (
<div>
<p>some messages...some messages...</p>
<p>some messages...some messages...</p>
</div>
),
onOk() {},
});
};
success = () => {
Modal.success({
title: 'This is a success message',
content: 'some messages...some messages...',
});
};
error = () => {
Modal.error({
title: 'This is an error message',
content: 'some messages...some messages...',
});
};
warning = () => {
Modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
render() {
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '对话框']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card bordered={false}>
<p>
<Button type="primary" onClick={this.showModal}>
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</p>
<p>
<Button type="primary" onClick={this.showModal2}>
</Button>
<Modal
title="Title of the modal dialog"
visible={this.state.visible2}
onOk={this.handleOk2}
confirmLoading={this.state.confirmLoading2}
onCancel={this.handleCancel2}
>
<p>{this.state.ModalText2}</p>
</Modal>
</p>
<p>
<Button type="primary" onClick={this.showModal3}>
</Button>
<Modal
visible={this.state.visible3}
title="Title"
onOk={this.handleOk3}
onCancel={this.handleCancel3}
footer={[
<Button
key="back"
size="large"
onClick={this.handleCancel3}
>
Return
</Button>,
<Button
key="submit"
type="primary"
size="large"
loading={this.state.loading3}
onClick={this.handleOk3}
>
Submit
</Button>,
]}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</p>
<p>
<Button onClick={this.showConfirm4}></Button>
</p>
<p>
<Button onClick={this.info}></Button>
<Button onClick={this.success}></Button>
<Button onClick={this.error}></Button>
<Button onClick={this.warning}></Button>
</p>
<p>
<Button
type="primary"
onClick={() => this.setModal1Visible(true)}
>
20px
</Button>
<Modal
title="20px to Top"
style={{ top: 20 }}
visible={this.state.modal1Visible}
onOk={() => this.setModal1Visible(false)}
onCancel={() => this.setModal1Visible(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
<br />
<br />
<Button
type="primary"
onClick={() => this.setModal2Visible(true)}
>
</Button>
<Modal
title="Vertically centered modal dialog"
wrapClassName="vertical-center-modal"
visible={this.state.modal2Visible}
onOk={() => this.setModal2Visible(false)}
onCancel={() => this.setModal2Visible(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</p>
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default S;

View File

@@ -0,0 +1,166 @@
/**
* Created by hao.cheng on 2017/4/25.
*/
import React, { Component } from 'react';
import { Row, Col, Card, Button, notification, Select } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { IconType, ConfigProps } from 'antd/lib/notification';
import { SmileOutlined } from '@ant-design/icons';
const { Option } = Select;
const options = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
class Notifications extends Component {
openNotification = () => {
notification.open({
message: 'Notification Title',
description:
'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
});
};
openNotification2 = () => {
const args = {
message: 'Notification Title',
description:
'I will never close automatically. I will be close automatically. I will never close automatically.',
duration: 0,
};
notification.open(args);
};
openNotificationWithIcon = (type: IconType) => {
notification[type]({
message: 'Notification Title',
description:
'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
});
};
openNotification3 = () => {
const key = `open${Date.now()}`;
const btnClick = function () {
// to hide notification box
notification.close(key);
};
const btn = (
<Button type="primary" size="small" onClick={btnClick}>
Confirm
</Button>
);
notification.open({
message: 'Notification Title',
description:
'A function will be be called after the notification is closed (automatically after the "duration" time of manually).',
btn,
key,
onClose: this.close,
});
};
close = () => {
console.log(
'Notification was closed. Either the close button was clicked or duration time elapsed.'
);
};
openNotification4 = () => {
notification.open({
message: 'Notification Title',
description:
'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
icon: <SmileOutlined style={{ color: '#108ee9' }} />,
});
};
render() {
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '通知提醒框']} />
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary" onClick={this.openNotification}>
-4.5
</Button>
</Card>
</div>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary" onClick={this.openNotification2}>
</Button>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button
type="primary"
onClick={() => this.openNotificationWithIcon('success')}
>
</Button>
<Button
type="primary"
onClick={() => this.openNotificationWithIcon('info')}
>
</Button>
<Button
type="primary"
onClick={() => this.openNotificationWithIcon('warning')}
>
</Button>
<Button
type="primary"
onClick={() => this.openNotificationWithIcon('error')}
>
</Button>
</Card>
</div>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary" onClick={this.openNotification3}>
</Button>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Button type="primary" onClick={this.openNotification4}>
</Button>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Select
defaultValue="topRight"
style={{ width: 120, marginRight: 10 }}
onChange={(val: ConfigProps['placement']) => {
notification.config({
placement: val,
});
}}
>
{options.map((val) => (
<Option key={val} value={val}>
{val}
</Option>
))}
</Select>
<Button type="primary" onClick={this.openNotification}>
</Button>
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default Notifications;

View File

@@ -0,0 +1,95 @@
/**
* Created by hao.cheng on 2017/4/23.
*/
import React from 'react';
import { Row, Col, Card, Spin, Alert, Switch, Button } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
class Spins extends React.Component {
state = { loading: false };
toggle = (value: boolean) => {
this.setState({ loading: value });
};
nprogressStart = () => {
NProgress.start();
};
nprogressDone = () => {
NProgress.done();
};
render() {
const container = (
<Alert
message="Alert message title"
description="Further details about the context of this alert."
type="info"
/>
);
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '加载中']} />
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Spin />
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card>
<Spin size="small" />
<Spin />
<Spin size="large" />
</Card>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Spin tip="Loading...">
<Alert
message="Alert message title"
description="Further details about the context of this alert."
type="info"
/>
</Spin>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<Spin spinning={this.state.loading}>{container}</Spin>
Loading state
<Switch checked={this.state.loading} onChange={this.toggle} />
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card bordered={false}>
<h4></h4>
<p>
<Button icon="caret-right" onClick={this.nprogressStart} />
<span> NProgress.start() </span>
</p>
<p style={{ marginTop: 20 }}>
<Button icon="caret-right" onClick={this.nprogressDone} />
<span> NProgress.done() </span>
</p>
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default Spins;

218
src/components/ui/Tabs.tsx Normal file
View File

@@ -0,0 +1,218 @@
/**
* Created by hao.cheng on 2017/4/25.
*/
import React, { Component } from 'react';
import { Row, Col, Card, Tabs, Radio, Button } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { RadioChangeEvent } from 'antd/lib/radio';
import { TabsPosition } from 'antd/lib/tabs';
import { AndroidOutlined, AppleOutlined } from '@ant-design/icons';
const TabPane = Tabs.TabPane;
type TabsCustomState = {
activeKey: string;
panes: any;
mode: TabsPosition;
};
class TabsCustom extends Component<any, TabsCustomState> {
constructor(props: any) {
super(props);
const panes = [
{ title: 'Tab 1', content: 'Content of Tab Pane 1', key: '1' },
{ title: 'Tab 2', content: 'Content of Tab Pane 2', key: '2' },
];
this.state = {
activeKey: panes[0].key,
panes,
mode: 'top',
};
}
newTabIndex: number = 0;
callback = (key: string) => {
console.log(key);
};
handleModeChange = (e: RadioChangeEvent) => {
const mode = e.target.value;
this.setState({ mode });
};
onChange = (activeKey: string) => {
this.setState({ activeKey });
};
onEdit = (targetKey: string | React.MouseEvent<HTMLElement>, action: 'add' | 'remove'): any => {
this[action](targetKey as string);
};
add = () => {
const panes = this.state.panes;
const activeKey = `newTab${this.newTabIndex++}`;
panes.push({ title: 'New Tab', content: 'New Tab Pane', key: activeKey });
this.setState({ panes, activeKey });
};
remove = (targetKey: string) => {
let activeKey = this.state.activeKey;
let lastIndex = 0;
this.state.panes.forEach((pane: any, i: number) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const panes = this.state.panes.filter((pane: any) => pane.key !== targetKey);
if (lastIndex >= 0 && activeKey === targetKey) {
activeKey = panes[lastIndex].key;
}
this.setState({ panes, activeKey });
};
render() {
const { mode } = this.state;
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '标签页']} />
<Row gutter={16}>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="基本-默认选中第一项" bordered={false}>
<Tabs defaultActiveKey="1" onChange={this.callback}>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
</Card>
</div>
<div className="gutter-box">
<Card title="带图标" bordered={false}>
<Tabs defaultActiveKey="2" style={{ height: 150 }}>
<TabPane
tab={
<span>
<AppleOutlined />
Tab 1
</span>
}
key="1"
>
Tab 1
</TabPane>
<TabPane
tab={
<span>
<AndroidOutlined />
Tab 2
</span>
}
key="2"
>
Tab 2
</TabPane>
</Tabs>
</Card>
</div>
<div className="gutter-box">
<Card title="卡片式风格" bordered={false}>
<Tabs onChange={this.callback} type="card">
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
</Card>
</div>
</Col>
<Col className="gutter-row" md={12}>
<div className="gutter-box">
<Card title="禁用某项" bordered={false}>
<Tabs defaultActiveKey="1">
<TabPane tab="Tab 1" key="1">
Tab 1
</TabPane>
<TabPane tab="Tab 2" disabled key="2">
Tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Tab 3
</TabPane>
</Tabs>
</Card>
</div>
<div className="gutter-box">
<Card title="带滚动" bordered={false}>
<Radio.Group
onChange={this.handleModeChange}
value={mode}
style={{ marginBottom: 8 }}
>
<Radio.Button value="top">Horizontal</Radio.Button>
<Radio.Button value="left">Vertical</Radio.Button>
</Radio.Group>
<Tabs
defaultActiveKey="1"
tabPosition={mode}
style={{ height: 150 }}
>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
<TabPane tab="Tab 4" key="4">
Content of tab 4
</TabPane>
<TabPane tab="Tab 5" key="5">
Content of tab 5
</TabPane>
<TabPane tab="Tab 6" key="6">
Content of tab 6
</TabPane>
<TabPane tab="Tab 7" key="7">
Content of tab 7
</TabPane>
<TabPane tab="Tab 8" key="8">
Content of tab 8
</TabPane>
<TabPane tab="Tab 9" key="9">
Content of tab 9
</TabPane>
</Tabs>
</Card>
</div>
<div className="gutter-box">
<Card title="带删除和新增" bordered={false}>
<div style={{ marginBottom: 16 }}>
<Button onClick={this.add}>ADD</Button>
</div>
<Tabs
hideAdd
onChange={this.onChange}
activeKey={this.state.activeKey}
type="editable-card"
// onEdit={this.onEdit}
>
{this.state.panes.map((pane: any) => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default TabsCustom;

View File

@@ -0,0 +1,195 @@
/**
* Created by hao.cheng on 2017/4/26.
*/
import React, { Component } from 'react';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
import { Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';
import draftToMarkdown from 'draftjs-to-markdown';
import { EditorState } from 'draft-js';
const rawContentState = {
entityMap: {
'0': {
type: 'IMAGE',
mutability: 'MUTABLE',
data: { src: 'http://i.imgur.com/aMtBIep.png', height: 'auto', width: '100%' },
},
},
blocks: [
{
key: '9unl6',
text: '',
type: 'unstyled',
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {},
},
{
key: '95kn',
text: ' ',
type: 'atomic',
depth: 0,
inlineStyleRanges: [],
entityRanges: [{ offset: 0, length: 1, key: 0 }],
data: {},
},
{
key: '7rjes',
text: '',
type: 'unstyled',
depth: 0,
inlineStyleRanges: [],
entityRanges: [],
data: {},
},
],
};
type WysiwygState = {
editorContent: any;
contentState: any;
editorState: EditorState | undefined;
};
class Wysiwyg extends Component {
state: WysiwygState = {
editorContent: undefined,
contentState: rawContentState,
editorState: undefined,
};
onEditorChange = (editorContent: any) => {
this.setState({
editorContent,
});
};
clearContent = () => {
this.setState({
contentState: '',
});
};
onContentStateChange = (contentState: any) => {
console.log('contentState', contentState);
};
onEditorStateChange = (editorState: any) => {
this.setState({
editorState,
});
};
imageUploadCallBack = (file: any) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
xhr.open('POST', 'https://api.imgur.com/3/image');
xhr.setRequestHeader('Authorization', 'Client-ID 8d26ccd12712fca');
const data = new FormData(); // eslint-disable-line no-undef
data.append('image', file);
xhr.send(data);
xhr.addEventListener('load', () => {
const response = JSON.parse(xhr.responseText);
resolve(response);
});
xhr.addEventListener('error', () => {
const error = JSON.parse(xhr.responseText);
reject(error);
});
});
render() {
const { editorContent, editorState } = this.state;
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '富文本']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="富文本编辑器" bordered={false}>
<Editor
editorState={editorState}
toolbarClassName="home-toolbar"
wrapperClassName="home-wrapper"
editorClassName="home-editor"
onEditorStateChange={this.onEditorStateChange}
toolbar={{
history: { inDropdown: true },
inline: { inDropdown: false },
list: { inDropdown: true },
textAlign: { inDropdown: true },
image: { uploadCallback: this.imageUploadCallBack },
}}
onContentStateChange={this.onEditorChange}
placeholder="请输入正文~~尝试@哦,有惊喜"
spellCheck
onFocus={() => {
console.log('focus');
}}
onBlur={() => {
console.log('blur');
}}
onTab={() => {
console.log('tab');
return true;
}}
localization={{
locale: 'zh',
translations: { 'generic.add': 'Test-Add' },
}}
mention={{
separator: ' ',
trigger: '@',
caseSensitive: true,
suggestions: [
{ text: 'A', value: 'AB', url: 'href-a' },
{ text: 'AB', value: 'ABC', url: 'href-ab' },
{ text: 'ABC', value: 'ABCD', url: 'href-abc' },
{ text: 'ABCD', value: 'ABCDDDD', url: 'href-abcd' },
{ text: 'ABCDE', value: 'ABCDE', url: 'href-abcde' },
{ text: 'ABCDEF', value: 'ABCDEF', url: 'href-abcdef' },
{
text: 'ABCDEFG',
value: 'ABCDEFG',
url: 'href-abcdefg',
},
],
}}
/>
<style>{`
.home-editor {
min-height: 300px;
}
`}</style>
</Card>
</div>
</Col>
<Col className="gutter-row" md={8}>
<Card title="同步转换HTML" bordered={false}>
<pre>{draftToHtml(editorContent)}</pre>
</Card>
</Col>
<Col className="gutter-row" md={8}>
<Card title="同步转换MarkDown" bordered={false}>
<pre style={{ whiteSpace: 'pre-wrap' }}>
{draftToMarkdown(editorContent)}
</pre>
</Card>
</Col>
<Col className="gutter-row" md={8}>
<Card title="同步转换JSON" bordered={false}>
<pre style={{ whiteSpace: 'normal' }}>
{JSON.stringify(editorContent)}
</pre>
</Card>
</Col>
</Row>
</div>
);
}
}
export default Wysiwyg;

View File

@@ -0,0 +1,57 @@
/**
* Created by hao.cheng on 2017/4/26.
*/
import React from 'react';
import BannerAnim, { Element } from 'rc-banner-anim';
import TweenOne from 'rc-tween-one';
import 'rc-banner-anim/assets/index.css';
const BgElement = Element.BgElement;
class AutoPlay extends React.Component {
render(){
return (
<BannerAnim prefixCls="banner-user" autoPlay>
<Element
prefixCls="banner-user-elem"
key="0"
>
<BgElement
key="bg"
className="bg"
style={{
background: '#364D79',
}}
/>
<TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
Ant Motion Banner
</TweenOne>
<TweenOne className="banner-user-text"
animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
>
The Fast Way Use Animation In React
</TweenOne>
</Element>
<Element
prefixCls="banner-user-elem"
key="1"
>
<BgElement
key="bg"
className="bg"
style={{
background: '#64CBCC',
}}
/>
<TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
Ant Motion Banner
</TweenOne>
<TweenOne className="banner-user-text"
animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
>
The Fast Way Use Animation In React
</TweenOne>
</Element>
</BannerAnim>);
}
}
export default AutoPlay;

View File

@@ -0,0 +1,60 @@
/**
* Created by hao.cheng on 2017/4/26.
*/
import React from 'react';
import BannerAnim, { Element } from 'rc-banner-anim';
import TweenOne from 'rc-tween-one';
import 'rc-banner-anim/assets/index.css';
const BgElement = Element.BgElement;
class Basic extends React.Component {
render(){
return (
<BannerAnim prefixCls="banner-user">
<Element
prefixCls="banner-user-elem"
key="0"
>
<BgElement
key="bg"
className="bg"
style={{
background: '#364D79',
}}
/>
<TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
Ant Motion Banner
</TweenOne>
<TweenOne
className="banner-user-text"
animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
>
The Fast Way Use Animation In React
</TweenOne>
</Element>
<Element
prefixCls="banner-user-elem"
key="1"
>
<BgElement
key="bg"
className="bg"
style={{
background: '#64CBCC',
}}
/>
<TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
Ant Motion Banner
</TweenOne>
<TweenOne
className="banner-user-text"
animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
>
The Fast Way Use Animation In React
</TweenOne>
</Element>
</BannerAnim>
);
}
}
export default Basic;

View File

@@ -0,0 +1,208 @@
/**
* Created by hao.cheng on 2017/4/26.
*/
import React from 'react';
import BannerAnim from 'rc-banner-anim';
import TweenOne, { TweenOneGroup } from 'rc-tween-one';
import 'rc-banner-anim/assets/index.css';
const { Element, Arrow, Thumb } = BannerAnim;
const BgElement = Element.BgElement;
type CustomState = {
intShow: number;
prevEnter: boolean;
nextEnter: boolean;
thumbEnter: boolean;
};
class Custom extends React.Component<any, CustomState> {
imgArray: string[];
constructor(props: any) {
super(props);
this.imgArray = [
'https://zos.alipayobjects.com/rmsportal/hzPBTkqtFpLlWCi.jpg',
'https://zos.alipayobjects.com/rmsportal/gGlUMYGEIvjDOOw.jpg',
];
this.state = {
intShow: 0,
prevEnter: false,
nextEnter: false,
thumbEnter: false,
};
}
onChange = (type: string, int: number) => {
if (type === 'before') {
this.setState({
intShow: int,
});
}
};
getNextPrevNumber() {
let nextInt = this.state.intShow + 1;
let prevInt = this.state.intShow - 1;
if (nextInt >= this.imgArray.length) {
nextInt = 0;
}
if (prevInt < 0) {
prevInt = this.imgArray.length - 1;
}
return [prevInt, nextInt];
}
prevEnter() {
this.setState({
prevEnter: true,
});
}
prevLeave() {
this.setState({
prevEnter: false,
});
}
nextEnter() {
this.setState({
nextEnter: true,
});
}
nextLeave() {
this.setState({
nextEnter: false,
});
}
onMouseEnter() {
this.setState({
thumbEnter: true,
});
}
onMouseLeave() {
this.setState({
thumbEnter: false,
});
}
render() {
const intArray = this.getNextPrevNumber();
const thumbChildren = this.imgArray.map((img, i) => (
<span key={i}>
<i style={{ backgroundImage: `url(${img})` }} />
</span>
));
return (
<BannerAnim
onChange={this.onChange}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
prefixCls="custom-arrow-thumb"
>
<Element key="aaa" prefixCls="banner-user-elem">
<BgElement
key="bg"
className="bg"
style={{
backgroundImage: `url(${this.imgArray[0]})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<TweenOne
className="banner-user-title"
animation={{ y: 30, opacity: 0, type: 'from' }}
>
Ant Motion Banner
</TweenOne>
<TweenOne
className="banner-user-text"
animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
>
The Fast Way Use Animation In React
</TweenOne>
</Element>
<Element key="bbb" prefixCls="banner-user-elem">
<BgElement
key="bg"
className="bg"
style={{
backgroundImage: `url(${this.imgArray[1]})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<TweenOne
className="banner-user-title"
animation={{ y: 30, opacity: 0, type: 'from' }}
>
Ant Motion Banner
</TweenOne>
<TweenOne
className="banner-user-text"
animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
>
The Fast Way Use Animation In React
</TweenOne>
</Element>
<Arrow
arrowType="prev"
key="prev"
prefixCls="user-arrow"
component={TweenOne}
onMouseEnter={this.prevEnter}
onMouseLeave={this.prevLeave}
// animation={{ left: this.state.prevEnter ? 0 : -120 }}
>
<div className="arrow" />
<TweenOneGroup
enter={{ opacity: 0, type: 'from' }}
leave={{ opacity: 0 }}
appear={false}
className="img-wrapper"
component="ul"
>
<li
style={{ backgroundImage: `url(${this.imgArray[intArray[0]]})` }}
key={intArray[0]}
/>
</TweenOneGroup>
</Arrow>
<Arrow
arrowType="next"
key="next"
prefixCls="user-arrow"
component={TweenOne}
onMouseEnter={this.nextEnter}
onMouseLeave={this.nextLeave}
// animation={{ right: this.state.nextEnter ? 0 : -120 }}
>
<div className="arrow" />
<TweenOneGroup
enter={{ opacity: 0, type: 'from' }}
leave={{ opacity: 0 }}
appear={false}
className="img-wrapper"
component="ul"
>
<li
style={{ backgroundImage: `url(${this.imgArray[intArray[1]]})` }}
key={intArray[1]}
/>
</TweenOneGroup>
</Arrow>
<Thumb
prefixCls="user-thumb"
key="thumb"
component={TweenOne}
// animation={{ bottom: this.state.thumbEnter ? 0 : -70 }}
>
{thumbChildren}
</Thumb>
</BannerAnim>
);
}
}
export default Custom;

View File

@@ -0,0 +1,45 @@
/**
* Created by hao.cheng on 2017/4/26.
*/
import React from 'react';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../../widget/BreadcrumbCustom';
import Basic from './Basic';
import AutoPlay from './AutoPlay';
// import Custom from './Custom';
class Banners extends React.Component {
render() {
return (
<div className="gutter-example button-demo">
<BreadcrumbCustom breads={['UI', '轮播图']} />
<Row gutter={16}>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="基本用法" bordered={false}>
<Basic />
</Card>
</div>
</Col>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="自动轮播-默认5秒" bordered={false}>
<AutoPlay />
</Card>
</div>
</Col>
<Col className="gutter-row" md={24}>
<div className="gutter-box">
<Card title="自定义左右箭头与缩略图" bordered={false}>
{/*引入自定义会导致组件冲突不显示*/}
{/*<Custom />*/}
</Card>
</div>
</Col>
</Row>
</div>
);
}
}
export default Banners;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
/**
* Created by hao.cheng on 2017/4/22.
*/
import React from 'react';
import PropTypes from 'prop-types';
import setFont from './iconfont';
setFont();
type EmojiProps = {
type: string;
};
const Emoji = ({ type }: EmojiProps) => {
const useTag = `<use xlink:href=${'#icon-' + type} />`;
return (
<i className="emoji">
<svg className="emoji" dangerouslySetInnerHTML={{ __html: useTag }} />
<style>{`
.emoji {
display: inline-block;
overflow: hidden;
}
.emoji svg {
width: 3em;
height: 3em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
`}</style>
</i>
);
};
Emoji.propTypes = {
type: PropTypes.string.isRequired,
};
export default Emoji;

View File

@@ -0,0 +1,21 @@
import React from 'react';
import ReactQMap from 'react-qmap';
const getContianer = (dom: any) => {
const middleControl = document.createElement('div');
middleControl.style.cssText =
'width: 22px;height: 30px;position: absolute;left: 50%;top: 50%;z-index: 999;margin-left: -23px;margin-top: -23px;';
middleControl.innerHTML = `<img src="${require('../../../style/imgs/spot_location.png')}" style="width: 100%;height: 100%;" />`;
dom.appendChild(middleControl);
};
export default () => (
<ReactQMap
center={{ latitude: 30.53786, longitude: 104.07265 }}
initialOptions={{ zoomControl: true, mapTypeControl: true }}
apiKey="UN6BZ-MP2W6-XWCSX-M2ATU-QORGZ-OWFOE"
style={{ height: 500 }}
mySpot={{ latitude: 30.53786, longitude: 104.07265 }}
getContainer={getContianer}
/>
);

View File

@@ -0,0 +1,19 @@
import React from 'react';
import Tencent from './Tencent';
import { Row, Col, Card } from 'antd';
import BreadcrumbCustom from '../../widget/BreadcrumbCustom';
export default () => (
<div>
<BreadcrumbCustom breads={['UI', '地图']} />
<Row gutter={16}>
<Col md={24}>
<div style={{ height: 500 }}>
<Card bordered={false} title="腾讯地图">
<Tencent />
</Card>
</div>
</Col>
</Row>
</div>
);

View File

@@ -0,0 +1,19 @@
/**
* Created by 叶子 on 2017/7/31.
*/
import { Component } from 'react';
import { connectAlita } from 'redux-alita';
type AuthWidgetProps = {
auth: any;
children: (param: any) => React.ReactElement;
};
class AuthWidget extends Component<AuthWidgetProps> {
render() {
const { auth = {} } = this.props;
return this.props.children(auth.data || {});
}
}
export default connectAlita(['auth'])(AuthWidget);

View File

@@ -0,0 +1,25 @@
/**
* Created by hao.cheng on 2017/4/22.
*/
import React, { ReactNode } from 'react';
import { Breadcrumb } from 'antd';
import { Link } from 'react-router-dom';
interface BreadcrumbCustomProps {
breads?: ReactNode[];
}
const BreadcrumbCustom = (props: BreadcrumbCustomProps) => {
const { breads } = props;
return (
<Breadcrumb style={{ margin: '12px 0' }}>
<Breadcrumb.Item>
<Link to={'/app/dashboard/index'}></Link>
</Breadcrumb.Item>
{breads?.map((bread, i) => (
<Breadcrumb.Item key={i}>{bread}</Breadcrumb.Item>
))}
</Breadcrumb>
);
};
export default BreadcrumbCustom;

View File

@@ -0,0 +1,19 @@
/*
* File: Copyright.tsx
* Desc: 版权信息
* File Created: 2020-04-12 22:50:33
* Author: chenghao
* ------
* Copyright 2020 - present, karakal
*/
import React from 'react';
const Copyright = () => {
return (
<div>
react-admin ©{new Date().getFullYear()} Created by yezihaohao@yezi.haohao@foxmail.com
</div>
);
};
export default Copyright;

View File

@@ -0,0 +1,9 @@
import React from 'react';
export default () => (
<div
style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}
>
...
</div>
);

View File

@@ -0,0 +1,62 @@
/*
* File: PwaInstaller.js
* Desc: pwa手动触发安装
* File Created: 2018-11-07 21:13:18
* Author: chenghao
*
* Copyright 2018 - present, chenghao
*/
import React, { Component } from 'react';
class PwaInstaller extends Component {
state = {
installed: true,
};
componentDidMount() {
window.addEventListener('beforeinstallprompt', this.beforeInstallPrompt);
}
componentWillUnmount() {
window.removeEventListener('beforeinstallprompt', this.beforeInstallPrompt);
}
deferredPrompt: any;
beforeInstallPrompt = (e: Event) => {
console.log('beforeinstallprompt Event fired');
// 未安装PWA应用
this.setState({ installed: false });
e.preventDefault();
// Stash the event so it can be triggered later.
this.deferredPrompt = e;
return false;
};
download = () => {
if (this.deferredPrompt !== undefined) {
// The user has had a postive interaction with our app and Chrome
// has tried to prompt previously, so let's show the prompt.
this.deferredPrompt.prompt();
// Follow what the user has done with the prompt.
this.deferredPrompt.userChoice.then((choiceResult: any) => {
console.log(choiceResult.outcome);
if (choiceResult.outcome === 'dismissed') {
console.log('User cancelled home screen install');
} else {
console.log('User added to home screen');
}
// We no longer need the prompt. Clear it up.
this.deferredPrompt = null;
});
}
};
render() {
const { installed } = this.state;
return (
!installed && (
<div className="installer" onClick={this.download}>
<div className="installer__btn" />
</div>
)
);
}
}
export default PwaInstaller;

View File

@@ -0,0 +1,43 @@
import React, { Component } from 'react';
import { SketchPicker } from 'react-color';
import classNames from 'classnames';
import { SettingOutlined } from '@ant-design/icons';
class ThemePicker extends Component {
state = {
switcherOn: false,
background: localStorage.getItem('@primary-color') || '#313653',
};
_switcherOn = () => {
this.setState({
switcherOn: !this.state.switcherOn,
});
};
_handleChangeComplete = (color: any) => {
console.log(color);
this.setState({ background: color.hex });
localStorage.setItem('@primary-color', color.hex);
(window as any).less.modifyVars({
'@primary-color': color.hex,
});
};
render() {
const { switcherOn, background } = this.state;
return (
<div className={classNames('switcher dark-white', { active: switcherOn })}>
<span className="sw-btn dark-white" onClick={this._switcherOn}>
<SettingOutlined type="setting" className="text-dark" />
</span>
<div style={{ padding: 10 }} className="clear">
<SketchPicker
color={background}
onChangeComplete={this._handleChangeComplete}
/>
</div>
</div>
);
}
}
export default ThemePicker;

View File

@@ -0,0 +1,4 @@
export { default as PwaInstaller } from './PwaInstaller';
export { default as AuthWidget } from './AuthWidget';
export { default as ThemePicker } from './ThemePicker';
export { default as Copyright } from './Copyright';

60
src/index.tsx Normal file
View File

@@ -0,0 +1,60 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { AlitaProvider, setConfig } from 'redux-alita';
import umbrella from 'umbrella-storage';
import Page from './Page';
import * as serviceWorker from './serviceWorker';
import * as apis from './service';
// import { AppContainer } from 'react-hot-loader';
import './style/lib/animate.css';
import './style/index.less';
import './style/antd/index.less';
setConfig(apis);
umbrella.config('REACT-ADMIN');
// const render = Component => { // 增加react-hot-loader保持状态刷新操作如果不需要可去掉并把下面注释的打开
// ReactDOM.render(
// <AppContainer>
// <Provider store={store}>
// <Component store={store} />
// </Provider>
// </AppContainer>
// ,
// document.getElementById('root')
// );
// };
// render(Page);
// Webpack Hot Module Replacement API
// if (module.hot) {
// // 隐藏You cannot change <Router routes>; it will be ignored 错误提示
// // react-hot-loader 使用在react-router 3.x上引起的提示react-router 4.x不存在
// // 详情可参照https://github.com/gaearon/react-hot-loader/issues/298
// const orgError = console.error; // eslint-disable-line no-console
// console.error = (...args) => { // eslint-disable-line no-console
// if (args && args.length === 1 && typeof args[0] === 'string' && args[0].indexOf('You cannot change <Router routes>;') > -1) {
// // React route changed
// } else {
// // Log the error as normally
// orgError.apply(console, args);
// }
// };
// module.hot.accept('./Page', () => {
// render(Page);
// })
// }
ReactDOM.render(
// <AppContainer>
<AlitaProvider>
<Page />
</AlitaProvider>,
// </AppContainer>
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
// serviceWorker.unregister();
serviceWorker.register();

1
src/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

75
src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1,75 @@
/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />
declare namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: 'development' | 'production' | 'test';
readonly PUBLIC_URL: string;
}
}
declare module '*.bmp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
const src: string;
export default src;
}
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module 'draftjs-to-html';
declare module 'draftjs-to-markdown';
declare module 'react-qmap';

View File

@@ -0,0 +1,48 @@
/*
* File: RouteWrapper.tsx
* Desc: 描述
* File Created: 2020-05-19 11:32:58
* Author: chenghao at <hao.cheng@karakal.com.cn>
* ------
* Copyright 2020 - present, karakal
*/
import React, { useMemo } from 'react';
import DocumentTitle from 'react-document-title';
import queryString from 'query-string';
const RouteWrapper = (props: any) => {
let { Comp, route, ...restProps } = props;
/** useMemo 缓存query避免每次生成生的query */
const queryMemo = useMemo(() => {
const queryReg = /\?\S*/g;
const matchQuery = (reg: RegExp) => {
const queryParams = restProps.location.search.match(reg);
return queryParams ? queryParams[0] : '{}';
};
return queryString.parse(matchQuery(queryReg));
}, [restProps.location.search]);
const mergeQueryToProps = () => {
const queryReg = /\?\S*/g;
const removeQueryInRouter = (restProps: any, reg: RegExp) => {
const { params } = restProps.match;
Object.keys(params).forEach((key) => {
params[key] = params[key] && params[key].replace(reg, '');
});
restProps.match.params = { ...params };
};
restProps = removeQueryInRouter(restProps, queryReg);
const merge = {
...restProps,
query: queryMemo,
};
return merge;
};
return (
<DocumentTitle title={route.title}>
<Comp {...mergeQueryToProps()} />
</DocumentTitle>
);
};
export default RouteWrapper;

157
src/routes/config.ts Normal file
View File

@@ -0,0 +1,157 @@
export interface IFMenuBase {
key: string;
title: string;
icon?: string;
component?: string;
query?: string;
requireAuth?: string;
route?: string;
/** 是否登录校验true不进行校验访客 */
login?: boolean;
}
export interface IFMenu extends IFMenuBase {
subs?: IFMenu[];
}
const menus: {
menus: IFMenu[];
others: IFMenu[] | [];
[index: string]: any;
} = {
menus: [
// 菜单相关路由
{ key: '/app/dashboard/index', title: '首页', icon: 'mobile', component: 'Dashboard' },
{
key: '/app/ui',
title: 'UI',
icon: 'scan',
subs: [
{ key: '/app/ui/buttons', title: '按钮', component: 'Buttons' },
{ key: '/app/ui/icons', title: '图标', component: 'Icons' },
{ key: '/app/ui/spins', title: '加载中', component: 'Spins' },
{ key: '/app/ui/modals', title: '对话框', component: 'Modals' },
{ key: '/app/ui/notifications', title: '通知提醒框', component: 'Notifications' },
{ key: '/app/ui/tabs', title: '标签页', component: 'Tabs' },
{ key: '/app/ui/banners', title: '轮播图', component: 'Banners' },
{ key: '/app/ui/wysiwyg', title: '富文本', component: 'WysiwygBundle' },
{ key: '/app/ui/drags', title: '拖拽', component: 'Drags' },
{ key: '/app/ui/gallery', title: '画廊', component: 'Gallery' },
{ key: '/app/ui/map', title: '地图', component: 'MapUi' },
],
},
{
key: '/app/animation',
title: '动画',
icon: 'rocket',
subs: [
{
key: '/app/animation/basicAnimations',
title: '基础动画',
component: 'BasicAnimations',
},
{
key: '/app/animation/exampleAnimations',
title: '动画案例',
component: 'ExampleAnimations',
},
],
},
{
key: '/app/table',
title: '表格',
icon: 'copy',
subs: [
{ key: '/app/table/basicTable', title: '基础表格', component: 'BasicTable' },
{ key: '/app/table/advancedTable', title: '高级表格', component: 'AdvancedTable' },
{
key: '/app/table/asynchronousTable',
title: '异步表格',
component: 'AsynchronousTable',
},
],
},
{
key: '/app/chart',
title: '图表',
icon: 'area-chart',
subs: [
{ key: '/app/chart/echarts', title: 'echarts', component: 'Echarts' },
{ key: '/app/chart/recharts', title: 'recharts', component: 'Recharts' },
],
},
{
key: '/subs4',
title: '页面',
icon: 'switcher',
subs: [
{ key: '/login', title: '登录' },
{ key: '/404', title: '404' },
],
},
{
key: '/app/auth',
title: '权限管理',
icon: 'safety',
subs: [
{ key: '/app/auth/basic', title: '基础演示', component: 'AuthBasic' },
{
key: '/app/auth/routerEnter',
title: '路由拦截',
component: 'RouterEnter',
requireAuth: 'auth/testPage',
},
],
},
{
key: '/app/cssModule',
title: 'cssModule',
icon: 'star',
component: 'Cssmodule',
},
{
key: '/app/extension',
title: '功能扩展',
icon: 'bars',
subs: [
{
key: '/app/extension/queryParams',
title: '问号形式参数',
component: 'QueryParams',
query: '?param1=1&param2=2',
},
{
key: '/app/extension/visitor',
title: '访客模式',
component: 'Visitor',
login: true,
},
{
key: '/app/extension/multiple',
title: '多级菜单',
subs: [
{
key: '/app/extension/multiple/child',
title: '多级菜单子菜单',
subs: [
{
key: '/app/extension/multiple/child/child',
title: '多级菜单子子菜单',
component: 'MultipleMenu',
},
],
},
],
},
{
key: '/app/extension/env',
title: '环境配置',
component: 'Env',
},
],
},
],
others: [], // 非菜单相关路由
};
export default menus;

72
src/routes/index.tsx Normal file
View File

@@ -0,0 +1,72 @@
/**
* Created by 叶子 on 2017/8/13.
*/
import React from 'react';
import { Route, Redirect, Switch } from 'react-router-dom';
import { useAlita } from 'redux-alita';
import umbrella from 'umbrella-storage';
import AllComponents from '../components';
import routesConfig, { IFMenuBase, IFMenu } from './config';
import { checkLogin } from '../utils';
import RouteWrapper from './RouteWrapper';
type CRouterProps = {
auth: any;
};
const CRouter = (props: CRouterProps) => {
const { auth } = props;
const [smenus] = useAlita({ smenus: null }, { light: true });
const getPermits = (): any[] | null => {
return auth ? auth.permissions : null;
};
const requireAuth = (permit: any, component: React.ReactElement) => {
const permits = getPermits();
if (!permits || !permits.includes(permit)) return <Redirect to={'404'} />;
return component;
};
const requireLogin = (component: React.ReactElement, permit: any) => {
const permits = getPermits();
if (!checkLogin(permits)) {
// 线上环境判断是否登录
return <Redirect to={'/login'} />;
}
return permit ? requireAuth(permit, component) : component;
};
const createMenu = (r: IFMenu) => {
const route = (r: IFMenuBase) => {
const Component = r.component && AllComponents[r.component];
return (
<Route
key={r.route || r.key}
exact
path={r.route || r.key}
render={(props: any) => {
// 重新包装组件
const wrapper = (
<RouteWrapper {...{ ...props, Comp: Component, route: r }} />
);
return r.login ? wrapper : requireLogin(wrapper, r.requireAuth);
}}
/>
);
};
const subRoute = (r: IFMenu): any =>
r.subs && r.subs.map((subR: IFMenu) => (subR.subs ? subRoute(subR) : route(subR)));
return r.component ? route(r) : subRoute(r);
};
const createRoute = (key: string) => routesConfig[key].map(createMenu);
const getAsyncMenus = () => smenus || umbrella.getLocalStorage('smenus') || [];
return (
<Switch>
{Object.keys(routesConfig).map((key) => createRoute(key))}
{getAsyncMenus().map(createMenu)}
<Route render={() => <Redirect to="/404" />} />
</Switch>
);
};
export default CRouter;

22
src/service/config.ts Normal file
View File

@@ -0,0 +1,22 @@
/**
* Created by 叶子 on 2017/7/30.
* 接口地址配置文件
*/
//easy-mock模拟数据接口地址
const MOCK_API = 'https://react-admin-mock.now.sh/api';
export const MOCK_AUTH_ADMIN = MOCK_API + '/admin.js'; // 管理员权限接口
export const MOCK_AUTH_VISITOR = MOCK_API + '/visitor.js'; // 访问权限接口
/** 服务端异步菜单接口 */
export const MOCK_MENU = MOCK_API + '/menu.js';
// github授权
export const GIT_OAUTH = 'https://github.com/login/oauth';
// github用户
export const GIT_USER = 'https://api.github.com/user';
// bbc top news
export const NEWS_BBC =
'https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=429904aa01f54a39a278a406acf50070';
export const TRAILWAY_API = 'http://localhost:8000';

49
src/service/index.ts Normal file
View File

@@ -0,0 +1,49 @@
/**
* Created by hao.cheng on 2017/4/16.
*/
import axios from 'axios';
import { get, post } from './tools';
import * as config from './config';
export const getBbcNews = () => get({ url: config.NEWS_BBC });
export const npmDependencies = () =>
axios
.get('./npm.json')
.then((res) => res.data)
.catch((err) => console.log(err));
export const weibo = () =>
axios
.get('./weibo.json')
.then((res) => res.data)
.catch((err) => console.log(err));
export const gitOauthLogin = () =>
get({
url: `${config.GIT_OAUTH}/authorize?client_id=792cdcd244e98dcd2dee&redirect_uri=http://localhost:3006/&scope=user&state=reactAdmin`,
});
export const gitOauthToken = (code: string) =>
post({
url: `https://cors-anywhere.herokuapp.com/${config.GIT_OAUTH}/access_token`,
data: {
client_id: '792cdcd244e98dcd2dee',
client_secret: '81c4ff9df390d482b7c8b214a55cf24bf1f53059',
redirect_uri: 'http://localhost:3006/',
state: 'reactAdmin',
code,
},
});
// {headers: {Accept: 'application/json'}}
export const gitOauthInfo = (access_token: string) =>
get({ url: `${config.GIT_USER}access_token=${access_token}` });
// easy-mock数据交互
// 管理员权限获取
export const admin = () => get({ url: config.MOCK_AUTH_ADMIN });
// 访问权限获取
export const guest = () => get({ url: config.MOCK_AUTH_VISITOR });
/** 获取服务端菜单 */
export const fetchMenu = () => get({ url: config.MOCK_MENU });
export const trailwayLogin = (data: any) => post({ url: config.TRAILWAY_API + '/login', data });

43
src/service/tools.ts Normal file
View File

@@ -0,0 +1,43 @@
/**
* Created by 叶子 on 2017/7/30.
* http通用工具函数
*/
import axios from 'axios';
import { message } from 'antd';
interface IFRequestParam {
url: string;
msg?: string;
config?: any;
data?: any;
}
/**
* 公用get请求
* @param url 接口地址
* @param msg 接口异常提示
* @param headers 接口所需header配置
*/
export const get = ({ url, msg = '接口异常', config }: IFRequestParam) =>
axios
.get(url, config)
.then((res) => res.data)
.catch((err) => {
console.log(err);
message.warn(msg);
});
/**
* 公用post请求
* @param url 接口地址
* @param data 接口参数
* @param msg 接口异常提示
* @param headers 接口所需header配置
*/
export const post = ({ url, data, msg = '接口异常', config }: IFRequestParam) =>
axios
.post(url, data, config)
.then((res) => res.data)
.catch((err) => {
console.log(err);
message.warn(msg);
});

143
src/serviceWorker.ts Normal file
View File

@@ -0,0 +1,143 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@@ -0,0 +1,16 @@
.header {
padding: 0;
height: 65px;
.ant-menu {
background: transparent;
color: @white;
.ant-menu-item {
&:hover {
color: @white;
}
}
}
}
.header__trigger {
color: @white;
}

View File

@@ -0,0 +1,6 @@
@import './variables.less';
@import './menu.less';
@import './utils.less';
@import './header.less';
@import './layout.less';
@import './reset.less';

View File

@@ -0,0 +1,14 @@
.ant-layout-content {
min-height: auto;
}
.ant-layout {
&.ant-layout-has-sider {
&.app_layout-mobile {
flex-direction: column;
.ant-layout-content {
margin: 0;
}
}
}
}

48
src/style/antd/menu.less Normal file
View File

@@ -0,0 +1,48 @@
.ant-menu-root {
&.ant-menu-inline, &.ant-menu-vertical {
background: @primary-color;
border-right: 1px solid @primary-color;
color: @white;
a {
color: @white;
}
.ant-menu-submenu-selected, {
color: @white;
}
.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, .ant-menu-submenu-active {
color: @white;
}
.ant-menu-submenu-title {
.ant-menu-submenu-arrow {
&::before, &::after {
background: @white;
}
}
&:hover {
color: @white;
.ant-menu-submenu-arrow {
&::before, &::after {
background: @white;
}
}
}
}
.ant-menu-submenu > .ant-menu {
background-color: @primary-color-light;
}
.ant-menu-item > a:hover {
color: @white;
}
}
}
.ant-menu-horizontal {
> .ant-menu-item-selected {
color: @white;
}
}
.sider-custom {
.ant-menu-submenu-title {
color: @white;
}
}

View File

@@ -0,0 +1,8 @@
/*
* File: reset.less
* Desc: 样式重写
* File Created: 2020-04-12 23:08:16
* Author: chenghao
* ------
* Copyright 2020 - present, karakal
*/

View File

@@ -0,0 +1,7 @@
[class*=btn] {
cursor: pointer
}
.bg--primary {
background: @primary-color;
}

View File

@@ -0,0 +1,18 @@
@import "../../../node_modules/antd/lib/style/themes/default.less";
// 基础颜色
@white: #ffffff;
@primary-color: #313653;
@primary-color-light: fade(lighten(@primary-color, 5%), 15%);
// 顶部背景色
@layout-header-background:@primary-color;
// 左边菜单light颜色
@layout-sider-background-light: #f9f9f9;
// 字体颜色
@text-color: #000000;
@table-selected-row-bg: #fbfbfb;
@primary-2: @primary-color-light;
// 基础圆角
@border-radius-base: 2px;
// 输入框后缀背景色
@input-addon-bg: @primary-color;

23
src/style/app.less Normal file
View File

@@ -0,0 +1,23 @@
/*
* File: app.less
* Desc: 描述
* File Created: 2020-07-26 18:27:37
* Author: yezi
* ------
* Copyright 2020 - present, yezi
*/
@prefix: app;
.@{prefix} {
&_layout {
flex-direction: column;
&_content {
margin: 0 16px;
overflow: initial;
flex: 1 1 0;
}
&_foot {
text-align: center;
}
}
}

122
src/style/banner.less Normal file
View File

@@ -0,0 +1,122 @@
.banner-user {
height: 200px;
}
.banner-user-elem {
text-align: center;
color: #fff;
position: relative;
overflow: hidden;
.banner-user-title {
font-size: 32px;
top: 40%;
}
.banner-user-text {
top: 40%;
}
}
.banner-anim-elem {
.bg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
}
.custom-arrow-thumb{
height: 220px;
.user-arrow {
top: 50%;
margin-top: -40px;
.img-wrapper {
width: 120px;
height: 80px;
float: left;
position: relative;
li {
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
position: absolute;
}
}
.arrow {
width: 20px;
height: 80px;
background: rgba(0, 0, 0, 0.3);
position: relative;
&:before, &:after {
width: 2px;
height: 15px;
background: #fff;
display: block;
content: ' ';
position: absolute;
}
}
&.next {
right: -120px;
.arrow {
float: left;
&:before {
-webkit-transform: rotate(-40deg);
transform: rotate(-40deg);
top: 28px;
left: 10px;
}
&:after {
-webkit-transform: rotate(40deg);
transform: rotate(40deg);
bottom: 27px;
left: 10px;
}
}
}
&.prev {
left: -120px;
.arrow {
float: right;
&:before {
-webkit-transform: rotate(40deg);
transform: rotate(40deg);
top: 28px;
left: 8px;
}
&:after {
-webkit-transform: rotate(-40deg);
transform: rotate(-40deg);
bottom: 27px;
left: 8px;
}
}
}
}
.user-thumb {
overflow: hidden;
background: rgba(255, 255, 255, 0.15);
height: 40px;
> span {
width: 50px;
height: 30px;
margin: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.15);
-webkit-transition: background .3s;
transition: background .3s;
background: transparent;
&.active {
background: rgba(255, 255, 255, 0.45);
}
i {
display: block;
width: 46px;
height: 26px;
margin: 2px;
background-size: cover;
background-position: center;
}
}
}
}

3
src/style/button.less Normal file
View File

@@ -0,0 +1,3 @@
.ant-btn+.ant-btn {
margin-left: 10px;
}

44
src/style/card.less Normal file
View File

@@ -0,0 +1,44 @@
.react-draggable, .cursor-move{
cursor: move;
strong {
background: #ddd;
border: 1px solid #999;
border-radius: 3px;
display: block;
margin-bottom: 10px;
padding: 3px 5px;
text-align: center;
}
}
.no-cursor {
cursor: auto;
}
.card-tool {
position: absolute;
right: 24px;
top: 24px;
}
.list-group {
.list-group-item {
position: relative;
display: block;
margin-bottom: -1px;
padding: 12px 16px;
background: transparent;
border: 1px solid #ddd;
border-color: rgba(120, 130, 140, 0.065);
border-width: 1px 0;
&:first-child {
border-top-width: 0;
}
&:last-child {
border-bottom-width: 0;
}
}
}
.no-padding {
.ant-card-body {
padding: 0 !important;
}
}

Binary file not shown.

71
src/style/global.less Normal file
View File

@@ -0,0 +1,71 @@
small {
opacity: .6;
}
.text-muted{
opacity: .6;
}
.clear{
display: block;
overflow: hidden;
}
.center {
display: flex;
justify-content: center;
align-items: center;
}
.y-center{
display: flex;
align-items: center;
}
.block{
display: block;
}
.inline {
display: inline;
}
.none{
display: none;
}
.b-white {
border-color: #ffffff;
}
.w-full {
width: 100%;
}
.w-auto {
width: auto;
}
.h-auto {
height: auto;
}
.h-full {
height: 100%;
}
.h-v {
height: 100vh;
}
.h-v-5 {
height: 50vh;
}
.pull-left {
float: left;
}
.pull-right {
float: right;
}
.w-40 {
width: 40px;
height: 40px;
line-height: 40px;
display: inline-block;
text-align: center;
}

25
src/style/icons.less Normal file
View File

@@ -0,0 +1,25 @@
ul.icons-list{
list-style: none;
overflow: hidden;
li{
float: left;
width: 10%;
text-align: center;
list-style: none;
cursor: pointer;
height: 100px;
transition: all .3s;
background-color: #fff;
&:hover{
background-color: #cccccc;
color: #fff;
}
i{
margin: 16px 0 10px;
}
span{
display: block;
text-align: center;
}
}
}

10
src/style/img.less Normal file
View File

@@ -0,0 +1,10 @@
img {
vertical-align: middle;
}
.img-responsive{
width: 100%;
height: auto;
}
.img-circle {
border-radius: 50%;
}

BIN
src/style/imgs/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
src/style/imgs/b1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
src/style/imgs/beauty.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/style/imgs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Some files were not shown because too many files have changed in this diff Show More