feature/2.0-sales-trade
尹诚诚 3 years ago
parent 19a487ca0d
commit 02f642bf96

1
.gitignore vendored

@ -127,3 +127,4 @@ dmypy.json
# Pyre type checker
.pyre/
node_modules

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="JSX" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/dashboard.iml" filepath="$PROJECT_DIR$/.idea/dashboard.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,7 @@
const rewireMobX = require('react-app-rewire-mobx');
module.exports = function override(config, env) {
config = rewireMobX(config, env);
return config;
};

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fastapi.iml" filepath="$PROJECT_DIR$/.idea/fastapi.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

@ -0,0 +1,19 @@
import pymssql
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def index():
return {'message': '你已经正确创建 FastApi 服务!'}
@app.get("/orderinfo-frontend/{data}")
async def say(data: str, q: int = None):
return {"data": data, "q": q}
# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
print(f'Hi world')

@ -0,0 +1 @@
uvicorn main:app --port 8000 --reload

15047
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,45 @@
{
"name": "haina-dashboard",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"antd": "^4.22.6",
"mobx": "^6.6.1",
"mobx-react": "^7.5.2",
"react": "^18.2.0",
"react-app-rewire-mobx": "^1.0.9",
"react-app-rewired": "^2.2.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -0,0 +1,39 @@
@import '~antd/dist/antd.css';
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@ -0,0 +1,69 @@
import './App.css';
import {
HomeOutlined,
TeamOutlined,
DashboardOutlined,
FileProtectOutlined,
} from '@ant-design/icons';
import {Layout, Menu, Image} from 'antd';
import {BrowserRouter, Route, Routes, NavLink} from "react-router-dom"
import React from 'react';
import Home from "./views/Home"
import Dashboard from "./views/Dashboard"
import Orders from "./views/Orders"
import Logo from './logo.png'
function App() {
const {Content, Footer, Sider} = Layout;
const menu_items = [
{key: 1, label: <NavLink to="/">主页</NavLink>, icon: <HomeOutlined/>},
{key: 2, label: <NavLink to="/orders">订单数据</NavLink>, icon: <FileProtectOutlined/>},
{key: 3, label: <NavLink to="/dashboard">仪表盘</NavLink>, icon: <DashboardOutlined/>},
{
key: 4,
label: '下拉菜单',
icon: <TeamOutlined/>,
children: [
{key: 41, label: <NavLink to="/order">子菜单1</NavLink>},
{key: 412, label: <NavLink to="/order">子菜单2</NavLink>},
]
},
];
return (
<BrowserRouter>
<Layout
style={{
minHeight: '100vh',
}}
>
<Sider collapsible={false} defaultCollapsed={false} breakpoint="lg"
collapsedWidth="0">
<Image src={Logo} preview={false}/>
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline" items={menu_items}/>
</Sider>
<Layout className="site-layout">
<Content>
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/orders" element={<Orders/>}/>
<Route path="/dashboard" element={<Dashboard/>}/>
</Routes>
</Content>
<Footer
style={{
textAlign: 'center',
}}
>
Hainatravel Dashboard ©2022 Created by IT
</Footer>
</Layout>
</Layout>
</BrowserRouter>
);
}
export default App;

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

@ -0,0 +1,50 @@
import React, {Component} from 'react';
import {Select} from 'antd';
class GroupSelect extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.defaultValue,
};
}
handleChange = (value) => {
this.setState({data: value})
};
render() {
return (
<div>
<Select
mode="multiple"
allowClear
style={{width: '100%',}}
placeholder="选择小组"
defaultValue={this.state.data}
onChange={this.handleChange}
>
<Select.Option key="1" value="1">CH直销</Select.Option>
<Select.Option key="2" value="2">CH大客户</Select.Option>
<Select.Option key="28" value="28">AH</Select.Option>
<Select.Option key="7" value="7">市场推广</Select.Option>
<Select.Option key="8" value="8">德语</Select.Option>
<Select.Option key="9" value="9">日语</Select.Option>
<Select.Option key="11" value="11">法语</Select.Option>
<Select.Option key="12" value="12">西语</Select.Option>
<Select.Option key="20" value="20">俄语</Select.Option>
<Select.Option key="21" value="21">意语</Select.Option>
<Select.Option key="10" value="10">商旅</Select.Option>
<Select.Option key="18" value="18">CT</Select.Option>
<Select.Option key="16" value="16">APP</Select.Option>
<Select.Option key="30" value="30">Trippest</Select.Option>
</Select>
</div>
);
}
}
export default GroupSelect;

@ -0,0 +1,117 @@
import React, {Component} from 'react';
import {Table, Button, DatePicker, Space, Radio} from 'antd';
import {SearchOutlined} from '@ant-design/icons';
import moment from 'moment';
import GroupSelect from './GroupSelect';
import * as config from '../config' ;
class MobileDeal extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
mobile_deal_data: []
};
this.group_ids = React.createRef();
this.data = {
date_type: 'applyDate',
applydate_check: 1,
startdate_check: 0,
startdate: moment().subtract(8, 'days'),//上周一
enddate: moment().subtract(2, 'days'),//上周日
}
}
componentDidMount() {
//this.asyncFetch();
}
asyncFetch = () => {
this.setState({loading: true});
let url = '/service-tourdesign/CountYDOrder?DEI_SNList=' + this.group_ids.current.state.data.toString();
if (this.data.date_type == 'applyDate') {
url += '&ApplydateCheck=1&OrderStartdateCheck=0';
} else {
url += '&ApplydateCheck=0&OrderStartdateCheck=1';
}
url += '&ApplydateStart=' + this.data.startdate.format(config.DATE_FORMAT) + '&ApplydateEnd=' + this.data.enddate.format(config.DATE_FORMAT) + '%2023:59';
url += '&OrderStartdateStart=' + this.data.startdate.format(config.DATE_FORMAT) + '&OrderStartdateEnd=' + this.data.enddate.format(config.DATE_FORMAT) + '%2023:59';
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
let table_data = []
json && json.map((item, index) => {
table_data.push({
key: index,
department_name: item.DEI_DepartmentName,
mobile_order: item.YDOrderNum + '/' + item.OrderNum + ' (' + (item.OrderNumRate * 100).toFixed(1) + '%)',
mobile_deal: item.YDOrderNumSUC + ' / ' + item.OrderNumSUC + ' (' + (item.OrderNumSUCRate * 100).toFixed(1) + '%)',
mobile_gross: item.YDML + ' / ' + item.ML + ' (' + (item.MLRate * 100).toFixed(1) + '%)',
})
})
this.setState({mobile_deal_data: table_data});
this.setState({loading: false});
})
.catch((error) => {
this.setState({loading: false});
console.log('fetch data failed', error);
});
};
onChange_datetype = (e) => {
this.data.date_type = e.target.value;
};
onChange_dataPicker = (dates) => {
this.data.startdate = dates[0];
this.data.enddate = dates[1];
}
render() {
const columns = [
{
title: '市场',
dataIndex: 'department_name',
key: 'department_name',
},
{
title: '移动订单/总订单',
dataIndex: 'mobile_order',
key: 'mobile_order',
},
{
title: '移动成交/总成交',
dataIndex: 'mobile_deal',
key: 'mobile_deal',
},
{
title: '移动毛利/总毛利',
dataIndex: 'mobile_gross',
key: 'mobile_gross',
},
];
return (
<div>
<h2>移动成交</h2>
<GroupSelect ref={this.group_ids} defaultValue={['1', '2', '28', '7', '8', '9', '11', '12', '20', '21']} />
<Space size="large">
<DatePicker.RangePicker format={config.DATE_FORMAT}
defaultValue={[this.data.startdate, this.data.enddate]}
onChange={this.onChange_dataPicker}/>
<Radio.Group defaultValue={this.data.date_type} onChange={this.onChange_datetype}>
<Radio value="applyDate">预定日期</Radio>
<Radio value="startDate">出发日期</Radio>
</Radio.Group>
<Button type="primary" icon={<SearchOutlined/>} loading={this.state.loading} onClick={() => {
this.asyncFetch();
}}>统计</Button>
</Space>
<Table dataSource={this.state.mobile_deal_data} columns={columns} pagination={false} size="small"/>
</div>
);
}
}
export default MobileDeal;

@ -0,0 +1,153 @@
import React, {Component} from 'react';
import {Table, Button, DatePicker, Space, Tooltip} from 'antd';
import {Line} from '@ant-design/charts';
import GroupSelect from './GroupSelect';
import SiteSelect from './SiteSelect';
import {SearchOutlined} from '@ant-design/icons';
import * as config from '../config' ;
import moment from "moment";
class OrdersTempTable extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
table: [],
show_table: false,
loading: false,
loading_detail: false
};
this.website_codes = React.createRef();
this.data = {
startdate: moment().subtract(7, 'days'),//上周一
enddate: moment().subtract(0, 'days'),//上周日
}
}
componentDidMount() {
}
asyncFetch = () => {
this.setState({loading: true});
this.setState({show_table: false});
let url = '/service-baseinfo/QueryWebData?type=orders_temp&db=1';
const website_code = "'" + this.website_codes.current.state.data.join("','") + "'";
url += '&WebSite=' + website_code + '&ApplyDateStart=' + this.data.startdate.format(config.DATE_FORMAT) + '&ApplyDateEnd=' + this.data.enddate.format(config.DATE_FORMAT) + '%2023:59';
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
this.setState({data: json.data})
this.setState({loading: false});
})
.catch((error) => {
this.setState({loading: false});
console.log('fetch data failed', error);
});
};
asyncFetch_detail = () => {
this.setState({loading_detail: true});
this.setState({show_table: true});
let url = '/service-baseinfo/QueryWebData?type=orders_temp_detail&db=1';
const website_code = "'" + this.website_codes.current.state.data.join("','") + "'";
url += '&WebSite=' + website_code + '&ApplyDateStart=' + this.data.startdate.format(config.DATE_FORMAT) + '&ApplyDateEnd=' + this.data.enddate.format(config.DATE_FORMAT) + '%2023:59';
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
this.setState({table: json.data})
this.setState({loading_detail: false});
})
.catch((error) => {
this.setState({loading_detail: false});
console.log('fetch data failed', error);
});
};
onChange_dataPicker = (dates) => {
this.data.startdate = dates[0];
this.data.enddate = dates[1];
}
render() {
const config = {
data: this.state.data,
padding: 'auto',
xField: 'order_date',
yField: 'order_count',
seriesField: 'order_sitecode',
xAxis: {
type: 'timeCat',
},
smooth: true,
};
const columns = [
{
title: '订单号',
dataIndex: 'COLI_ID',
key: 'COLI_ID',
},
{
title: '设备',
dataIndex: 'COLI_OrderSource',
key: 'COLI_OrderSource',
},
{
title: '类型',
dataIndex: 'COLI_SourceType',
key: 'COLI_SourceType',
},
{
title: '网站',
dataIndex: 'COLI_WebCode',
key: 'COLI_WebCode',
},
{
title: 'IP',
dataIndex: 'COLI_SenderIP',
key: 'COLI_SenderIP',
},
{
title: '预定时间',
dataIndex: 'COLI_ApplyDate',
key: 'COLI_ApplyDate',
},
{
title: '订单内容',
dataIndex: 'COLI_OrderDetailText',
key: 'COLI_OrderDetailText',
ellipsis: true,
},
];
return (
<div>
<h2>临时订单数量</h2>
<SiteSelect ref={this.website_codes}
defaultValue={['CHT', 'AH']}/>
<Space>
<DatePicker.RangePicker format={config.DATE_FORMAT}
defaultValue={[this.data.startdate, this.data.enddate]}
onChange={this.onChange_dataPicker}/>
<Button type="primary" icon={<SearchOutlined/>} loading={this.state.loading} onClick={() => {
this.asyncFetch();
}}>统计</Button>
<Button type="primary" icon={<SearchOutlined/>} loading={this.state.loading_detail} onClick={() => {
this.asyncFetch_detail();
}}>查看详情</Button>
</Space>
{this.state.show_table ? (
<Table dataSource={this.state.table} columns={columns} size="small"/>
) : (
<Line {...config} />
)}
</div>
);
}
}
export default OrdersTempTable;

@ -0,0 +1,50 @@
import React, {Component} from 'react';
import {Select} from 'antd';
class SiteSelect extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.defaultValue,
};
}
handleChange = (value) => {
this.setState({data: value})
};
render() {
return (
<div>
<Select
mode="multiple"
allowClear
style={{width: '100%',}}
placeholder="选择网站"
defaultValue={this.state.data}
onChange={this.handleChange}
>
{/*<Select.Option key="1" value="ALL">ALL</Select.Option>*/}
<Select.Option key="2" value="CHT">CHT</Select.Option>
<Select.Option key="28" value="GHKYZG">客运中国</Select.Option>
<Select.Option key="7" value="GHKYHW">客运海外</Select.Option>
<Select.Option key="8" value="AH">AH</Select.Option>
<Select.Option key="9" value="GH">GH</Select.Option>
<Select.Option key="11" value="JP">日语JP</Select.Option>
<Select.Option key="12" value="VAC">西语VAC</Select.Option>
<Select.Option key="20" value="IT">意大利语IT</Select.Option>
<Select.Option key="21" value="GM">德语GM</Select.Option>
<Select.Option key="10" value="RU">俄语RU</Select.Option>
<Select.Option key="18" value="VC">法语VC</Select.Option>
<Select.Option key="16" value="CT">CT</Select.Option>
<Select.Option key="30" value="trippest">TP</Select.Option>
</Select>
</div>
);
}
}
export default SiteSelect;

@ -0,0 +1,2 @@
export const DATE_FORMAT = 'YYYY-MM-DD';
export const HT_HOST = 'https://p9axztuwd7x8a7.mycht.cn';

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

@ -0,0 +1,23 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
import {Provider} from "mobx-react";
import stores from "./stores/Index";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider {...stores}>
<React.StrictMode>
<App/>
</React.StrictMode>
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

@ -0,0 +1,13 @@
import {observable, action} from 'mobx';
class DashboardStore {
constructor(app) {
this.app = app;
}
@observable countryList;
}
export default DashboardStore;

@ -0,0 +1,22 @@
import React from "react";
import {observable, action} from 'mobx';
import DashboardStore from "./DashboardStore";
import OrdersStore from "./OrdersStore";
class Index {
constructor(history) {
this.history = history;
this.pathname = history.location.pathname;
this.dashboard_store = new DashboardStore();
this.orders_store = new OrdersStore();
}
@action goBack() {
this.history.goBack();
}
@observable animating = false;
}
export default Index;

@ -0,0 +1,9 @@
import {observable, action} from 'mobx';
class OrdersStore {
@observable countryList;
}
export default OrdersStore;

@ -0,0 +1,191 @@
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength,padString) {
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String((typeof padString !== 'undefined' ? padString : ' '));
if (this.length > targetLength) {
return String(this);
}
else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0,targetLength) + String(this);
}
};
}
export function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
export function named(value) {
return function (target) {
target.definedName = value;
}
}
export function formatCurrency(name) {
if (name === 'USD' ) {
return '$';
} else if (name === 'RMB') {
return '¥';
} else if (name === 'EUR') {
return '€'
} else if (name === 'GBP') {
return '£'
} else {
return name + ' ';
}
}
export function formatPrice(price) {
return Math.ceil(price).toLocaleString();
}
export function formatPercent(number) {
return Math.round(number * 100) + '%'
}
export function formatDate(date) {
if (isEmpty(date)) { return 'NaN'; }
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const monthStr = ('' + month).padStart(2, 0);
const dayStr = ('' + day).padStart(2, 0);
const formatted = year + '-' + monthStr + '-' + dayStr;
return formatted;
}
export function formatTime(date) {
const hours = date.getHours();
const minutes = date.getMinutes();
const hoursStr = ('' + hours).padStart(2, 0);
const minutesStr = ('' + minutes).padStart(2, 0);
const formatted = hoursStr + ':' + minutesStr;
return formatted;
}
export function formatDatetime(date) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const monthStr = ('' + month).padStart(2, 0);
const dayStr = ('' + day).padStart(2, 0);
const hours = date.getHours();
const minutes = date.getMinutes();
const hoursStr = ('' + hours).padStart(2, 0);
const minutesStr = ('' + minutes).padStart(2, 0);
const formatted = year + '-' + monthStr + '-' + dayStr + ' ' + hoursStr + ':' + minutesStr;
return formatted;
}
export function mixins(...list) {
return function (target) {
list.forEach(val => {
const mixinObj = Object.create(val.prototype, {})
const name = Object.getPrototypeOf(mixinObj).constructor.name;
const camelCase = name.substr(0, 1).toLowerCase() + name.substr(1);
Object.assign(target.prototype, {[camelCase]: mixinObj})
})
}
}
export function camelCase(name) {
return name.substr(0, 1).toLowerCase() + name.substr(1);
}
export class UrlBuilder {
constructor(url) {
this.url = url;
this.paramList = [];
}
append(name, value) {
if (isNotEmpty(value)) {
this.paramList.push({'name': name, 'value': value});
}
return this;
}
build() {
this.paramList.forEach((e, i, a) => {
if (i === 0) {
this.url += '?';
} else {
this.url += '&';
}
this.url += e.name + '=' + e.value;
});
return this.url;
}
}
export function isNotEmpty(val) {
return val !== undefined && val !== null && val !== '';
}
export function isEmpty(val) {
return val === undefined || val === null || val === '';
}
export function empty(a) {
if (a === "") return true; //检验空字符串
if (a === "null") return true; //检验字符串类型的null
if (a === "undefined") return true; //检验字符串类型的 undefined
if (!a && a !== 0 && a !== "") return true; //检验 undefined 和 null
if (Array.prototype.isPrototypeOf(a) && a.length === 0) return true; //检验空数组
if (Object.prototype.isPrototypeOf(a) && Object.keys(a).length === 0) return true; //检验空对象
return false;
}
export function prepareUrl(url) {
return new UrlBuilder(url);
}
export function debounce(fn, delay = 500) {
let timer;
return (e) => {
e.persist();
clearTimeout(timer);
timer = setTimeout(() => {
fn(e);
}, delay);
};
};
export function throttle(fn, delay, atleast) {
let timeout = null,
startTime = new Date();
return function() {
let curTime = new Date();
clearTimeout(timeout);
if (curTime - startTime >= atleast) {
fn();
startTime = curTime;
} else {
timeout = setTimeout(fn, delay);
}
}
}
export function clickUrl(url) {
const httpLink = document.createElement('a');
httpLink.href = url;
httpLink.target = '_blank';
httpLink.click();
}

@ -0,0 +1,69 @@
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
const message =
'Fetch error: ' + response.url + ' ' + response.status + ' (' +
response.statusText + ')';
const error = new Error(message);
error.response = response;
throw error;
}
}
export function fetchText(url) {
return fetch(url)
.then(checkStatus)
.then(response => response.text())
.catch(error => {
throw error;
});
}
export function fetchJSON(url) {
return fetch(url)
.then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function postForm(url, data) {
return fetch(url, {
method: 'POST',
body: data
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function postJSON(url, obj) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function postStream(url, obj) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/octet-stream'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}

@ -0,0 +1,38 @@
import React, {Component} from 'react';
import {Col, Row} from "antd";
import OrdersTempTable from "../charts/OrdersTempTable";
import MobileDeal from "../charts/MobileDeal";
class Dashboard extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div
className="site-layout-background"
style={{
padding: 16,
minHeight: 480,
}}
>
<Row gutter={[16, {xs: 8, sm: 16, md: 24, lg: 32}]}>
<Col className="gutter-row" span={12}>
<OrdersTempTable/>
</Col>
<Col className="gutter-row" span={12}>
<MobileDeal/>
</Col>
</Row>
</div>
</div>
);
}
}
export default Dashboard;

@ -0,0 +1,19 @@
import React, {Component} from 'react';
class Home extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
这就是一个主页
</div>
);
}
}
export default Home;

@ -0,0 +1,19 @@
import React, {Component} from 'react';
class Orders extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
Orders
</div>
);
}
}
export default Orders;

@ -0,0 +1 @@
npm start

Binary file not shown.
Loading…
Cancel
Save