Reinit project

This commit is contained in:
Limo Saplf
2019-08-31 20:44:15 +08:00
commit b51ae824c7
115 changed files with 20955 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
/**
* Routes:
* - ./src/pages/routes
*/
import React from 'react';
import { connect, DispatchProp } from 'dva';
import { RouterTypes, Redirect } from 'umi';
import Tab from '@/components/Tab';
import styles from './style.less';
import { HomeModelState } from './model';
const HomePage: React.FC<RouterTypes & DispatchProp & HomeModelState> = ({
dispatch,
tabItems,
selectedTab,
location: { pathname },
children,
}) => {
if (pathname === '/home') return <Redirect to="/home/home" />
return (
<div className={styles.home}>
<section className={styles.page}>
{children}
</section>
<footer className={styles.tab}>
<Tab
items={tabItems}
selectedKey={selectedTab}
onChange={payload => dispatch({ type: 'home/changeTab', payload })}
/>
</footer>
</div>
);
}
export default connect(
({ home }: any) => ({ ...home }),
)(HomePage);

View File

@@ -0,0 +1,103 @@
import React, { useEffect } from 'react';
import { Radio } from 'antd';
import FittedImage from 'react-fitted-image';
import { connect, DispatchProp } from 'dva';
import { formatMessage } from 'umi-plugin-locale';
import List from '@/components/List';
import Label from '@/components/Label';
import { HomeEstateModelState } from './model';
import { EstateType, Estate } from '@/types/common';
import { dispatch } from '@/utils/utils';
import styles from './style.less';
const RadioGroup = Radio.Group;
const RadioButton = Radio.Button;
const renderItem = (data: Estate) => {
const holdTab = data.type === EstateType.Hold;
return (
<div
className={styles.item}
onClick={() => dispatch({ type: 'homeEstate/gotoDetail', payload: data })}
>
<div className="avatar">
<FittedImage src={data.image || ''} fit="cover" />
</div>
<div className="content">
<span className="name">{data.name}</span>
<span className="desc">
{
holdTab ?
formatMessage({ id: 'home.estate.timeDue' }, { time: data.lockTime }) :
formatMessage({ id: 'home.estate.timeSold' }, { time: data.outTime })
}
</span>
</div>
<div className="postfix">
<span className={`cost-msg ${holdTab ? 'primary' : ''}`}>
{formatMessage({ id: 'home.estate.cost' })} <span>{data.inPrice}</span> LOG
</span>
{!holdTab && <span className="sold-msg">
{formatMessage({ id: 'home.estate.profit' })} {data.profitPrice} LOG
</span>}
{holdTab && <span className="hold-msg">
{formatMessage({ id: 'home.estate.profit2' })}{data.profitShow}
</span>}
</div>
</div>
);
};
const EstatePage: React.FC<HomeEstateModelState & DispatchProp> = ({
dispatch,
loading,
refreshing,
tab,
data,
num,
}) => {
useEffect(() => {
if (!data.length) {
dispatch({ type: 'homeEstate/refresh' });
}
}, []);
return (
<div className={styles.container}>
<div className={styles.header}>
<RadioGroup
buttonStyle="solid"
value={tab}
onChange={e => dispatch({ type: 'homeEstate/changeTab', payload: e.target.value })}
>
<RadioButton value={EstateType.Hold}>{formatMessage({ id: 'home.estate.hold' })}</RadioButton>
<RadioButton value={EstateType.Sold}>{formatMessage({ id: 'home.estate.sold' })}</RadioButton>
</RadioGroup>
</div>
<Label
name={formatMessage({
id: tab === EstateType.Hold ? 'home.estate.holdTitle' : 'home.estate.soldTitle',
}, { num })}
className={styles.label}
/>
<List
className={styles.list}
data={data}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={() => dispatch({ type: 'homeEstate/refresh' })}
loading={loading}
onLoadMore={() => dispatch({ type: 'homeEstate/loadMore' })}
itemKey={data => data.id}
itemSize={80}
/>
</div>
);
};
export default connect(
({ homeEstate } : any) => ({ ...homeEstate }),
)(EstatePage);

View File

@@ -0,0 +1,110 @@
import { Model, routerRedux } from 'dva';
import { message } from 'antd';
import { EstateType, Estates } from '@/types/common';
import { getEstateList, EstateList } from './service';
export interface HomeEstateModelState {
refreshing: boolean;
loading: boolean;
loadingFinished: boolean;
data: Estates;
num: number;
pageIndex: number;
tab: EstateType;
}
const EstateModel: Model = {
namespace: 'homeEstate',
state: {
refreshing: false,
loading: false,
enableLoad: true,
loadingFinished: false,
data: [],
num: 0,
pageIndex: 1,
pageSize: 10,
tab: EstateType.Hold,
} as HomeEstateModelState,
effects: {
*refresh(_, { put, call, select }) {
const {
pageSize,
tab,
} = yield select((state: any) => state.homeEstate);
yield put({ type: 'onUpdateState', payload: { refreshing: true, loading: false } });
try {
const { total, list }: EstateList = yield call(getEstateList, 1, pageSize, tab);
const { tab: newTab } = yield select((state: any) => state.homeEstate);
if (tab === newTab) {
yield put({
type: 'onUpdateState',
payload: {
refreshing: false,
pageIndex: 2,
data: list,
num: total,
},
});
}
} catch (e) {
message.error(e.message);
yield put({ type: 'onUpdateState', payload: { refreshing: false, pageIndex: 1 } });
}
},
*loadMore(_, { put, call, select }) {
const {
pageIndex,
pageSize,
tab,
data,
} = yield select((state: any) => state.homeEstate);
yield put({ type: 'onUpdateState', payload: { loading: true, refreshing: false } });
try {
const { total, list }: EstateList = yield call(getEstateList, pageIndex, pageSize, tab);
const { tab: newTab } = yield select((state: any) => state.homeEstate);
if (tab === newTab) {
yield put({
type: 'onUpdateState',
payload: {
loading: false,
pageIndex: pageIndex + 1,
data: [...data, ...list],
num: total,
enableLoad: list.length === pageSize,
},
});
}
} catch (e) {
message.error(e.message);
yield put({ type: 'onUpdateState', payload: { loading: false } });
}
},
*changeTab({ payload: tab }, { put }) {
yield put({ type: 'onUpdateState', payload: { tab, pageIndex: 1, data: [], num: 0 } });
yield put({ type: 'refresh' });
},
*gotoDetail({ payload }, { put }) {
if (!payload) return;
yield put(routerRedux.push('/estate'));
yield put({ type: 'ext/onUpdateState', payload: { estateExt: payload } });
},
},
reducers: {
onUpdateState(state, { payload } : any) {
return {
...state,
...payload,
};
},
},
};
export default EstateModel;

View File

@@ -0,0 +1,22 @@
import http from '@/utils/http';
import { Estates, EstateType } from '@/types/common';
import { calcEstate } from '@/utils/transform';
export interface EstateList {
total: number;
list: Estates;
}
export async function getEstateList(pageIndex: number, pageSize: number, type: EstateType) {
const {
ok,
data,
msg,
} = await http.get<EstateList>('/estateorder/list', { type, pageIndex, pageSize });
if (ok && data) {
data.list.forEach(it => it.type = type);
data.list.forEach(calcEstate);
return data;
}
throw Error(msg);
}

View File

@@ -0,0 +1,96 @@
@import '../../../global.less';
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
}
.header {
height: @header-height;
line-height: @header-height;
text-align: center;
}
.label {
margin: 0;
padding: 10px;
background-color: @bg-color;
}
.list {
flex-grow: 1;
}
.item {
height: 100%;
display: flex;
align-items: stretch;
padding: 0 10px;
transition: background-color .2s;
&:active {
background-color: @bg-color;
}
:global {
.avatar {
flex-shrink: 0;
align-self: center;
@size: 56px;
width: @size;
height: @size;
overflow: hidden;
border-radius: 4px;
}
.content {
margin: 0 10px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.desc {
font-weight: lighter;
font-size: 0.8rem;
margin-top: 6px;
}
}
.postfix {
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
font-size: 0.8rem;
font-weight: lighter;
.cost-msg {
margin-bottom: 6px;
&.primary {
color: @primary-color;
}
> span {
font-size: 1.2rem;
}
}
.hold-msg {
background-color: @bg-primary-color;
color: @primary-color;
}
.sold-msg {
color: @primary-color;
}
}
}
}

View File

@@ -0,0 +1,7 @@
import React from 'react';
const FundPage: React.FC = () => {
return <div>Fund</div>
}
export default FundPage;

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { formatMessage } from 'umi-plugin-locale';
import Label from '@/components/Label';
import styles from './style.less';
const AdCard: React.FC = () => (
<section className={styles.adcard}>
<code>// TODO</code>
</section>
);
const HomePage: React.FC = () => {
const header = (
<div className={styles.header}>
<span className="title">{formatMessage({ id: 'home.home.statistic' })}</span>
<span className="label">
1000
<span className="unit">LOG</span>
</span>
</div>
);
return (
<div className={styles.container}>
{header}
<div className={styles.body}>
<AdCard />
<Label name={formatMessage({ id: 'home.home.header' })} />
</div>
</div>
);
}
export default HomePage;

View File

@@ -0,0 +1,17 @@
import { Model } from 'dva';
export interface HomeHomeModelState {
total: string;
}
const model: Model = {
namespace: 'homeHome',
state: {},
effects: {},
reducers: {},
};
export default model;

View File

@@ -0,0 +1,57 @@
@import '../../../global.less';
@headerHeight: 36%;
@offset: 10%;
.container {
position: relative;
height: 100%;
}
.header {
position: absolute;
top: 0;
left: 0;
height: @headerHeight;
width: 100%;
background: linear-gradient(to right, @primary-color, @secondary-color);
color: white;
display: flex;
flex-direction: column;
align-items: center;
:global {
.title {
font-size: 0.8rem;
margin: 14% 0 0;
font-weight: lighter;
}
.label {
font-size: 2.2rem;
font-weight: lighter;
}
.unit {
font-size: 1.2rem;
font-weight: bold;
display: inline-block;
margin-left: 8px;
}
}
}
.body {
position: relative;
z-index: 1;
height: 100%;
padding: @headerHeight + @offset 14px 0;
}
.adcard {
box-shadow: 1px 1px 10px lighten(@secondary-color, 18%);
border-radius: 4px;
height: 96px;
background-color: #fff;
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import FittedImage from 'react-fitted-image';
import banner from '@/assets/m_banner.jpg';
import styles from './style.less';
const MarketPage: React.FC = () => {
return (
<div className={styles.container}>
<div className={styles.banner}>
<FittedImage src={banner} fit="cover" />
</div>
</div>
);
}
export default MarketPage;

View File

@@ -0,0 +1,110 @@
import { Model, routerRedux } from 'dva';
import { message } from 'antd';
import { EstateType, Estates } from '@/types/common';
import { getEstateList, EstateList } from './service';
export interface HomeMarketModelState {
refreshing: boolean;
loading: boolean;
loadingFinished: boolean;
data: Estates;
num: number;
pageIndex: number;
tab: EstateType;
}
const MarketModel: Model = {
namespace: 'homeMarket',
state: {
refreshing: false,
loading: false,
enableLoad: true,
loadingFinished: false,
data: [],
num: 0,
pageIndex: 1,
pageSize: 10,
tab: EstateType.Hold,
} as HomeMarketModelState,
effects: {
*refresh(_, { put, call, select }) {
const {
pageSize,
tab,
} = yield select((state: any) => state.homeEstate);
yield put({ type: 'onUpdateState', payload: { refreshing: true, loading: false } });
try {
const { total, list }: EstateList = yield call(getEstateList, 1, pageSize, tab);
const { tab: newTab } = yield select((state: any) => state.homeEstate);
if (tab === newTab) {
yield put({
type: 'onUpdateState',
payload: {
refreshing: false,
pageIndex: 2,
data: list,
num: total,
},
});
}
} catch (e) {
message.error(e.message);
yield put({ type: 'onUpdateState', payload: { refreshing: false, pageIndex: 1 } });
}
},
*loadMore(_, { put, call, select }) {
const {
pageIndex,
pageSize,
tab,
data,
} = yield select((state: any) => state.homeEstate);
yield put({ type: 'onUpdateState', payload: { loading: true, refreshing: false } });
try {
const { total, list }: EstateList = yield call(getEstateList, pageIndex, pageSize, tab);
const { tab: newTab } = yield select((state: any) => state.homeEstate);
if (tab === newTab) {
yield put({
type: 'onUpdateState',
payload: {
loading: false,
pageIndex: pageIndex + 1,
data: [...data, ...list],
num: total,
enableLoad: list.length === pageSize,
},
});
}
} catch (e) {
message.error(e.message);
yield put({ type: 'onUpdateState', payload: { loading: false } });
}
},
*changeTab({ payload: tab }, { put }) {
yield put({ type: 'onUpdateState', payload: { tab, pageIndex: 1, data: [], num: 0 } });
yield put({ type: 'refresh' });
},
*gotoDetail({ payload }, { put }) {
if (!payload) return;
yield put(routerRedux.push('/estate'));
yield put({ type: 'ext/onUpdateState', payload: { estateExt: payload } });
},
},
reducers: {
onUpdateState(state, { payload } : any) {
return {
...state,
...payload,
};
},
},
};
export default MarketModel;

View File

@@ -0,0 +1,22 @@
import http from '@/utils/http';
import { Estates, EstateType } from '@/types/common';
import { calcEstate } from '@/utils/transform';
export interface EstateList {
total: number;
list: Estates;
}
export async function getEstateList(pageIndex: number, pageSize: number, type: EstateType) {
const {
ok,
data,
msg,
} = await http.get<EstateList>('/estateorder/list', { type, pageIndex, pageSize });
if (ok && data) {
data.list.forEach(it => it.type = type);
data.list.forEach(calcEstate);
return data;
}
throw Error(msg);
}

View File

@@ -0,0 +1,8 @@
.container {
height: 100%;
}
.banner {
width: 100%;
height: 240px;
}

107
src/pages/home/model.ts Normal file
View File

@@ -0,0 +1,107 @@
import { Model, routerRedux } from "dva";
import _ from 'lodash';
import { TabItemModels } from '@/components/Tab';
import homeIcon from '@/assets/home.svg';
import homeIconS from '@/assets/home_s.svg';
import estateIcon from '@/assets/estate.svg';
import estateIconS from '@/assets/estate_s.svg';
import marketIcon from '@/assets/market.svg';
import marketIconS from '@/assets/market_s.svg';
import fundIcon from '@/assets/fund.svg';
import fundIconS from '@/assets/fund_s.svg';
import profileIcon from '@/assets/profile.svg';
import profileIconS from '@/assets/profile_s.svg';
export interface HomeModelState {
tabItems: TabItemModels;
selectedTab: string;
}
const tabItems: TabItemModels = [
{
key: '/home/home',
name: 'home.tabHome',
icon: homeIcon,
iconSelected: homeIconS,
},
{
key: '/home/estate',
name: 'home.tabEstate',
icon: estateIcon,
iconSelected: estateIconS,
},
{
key: '/home/market',
name: 'home.tabMarket',
icon: marketIcon,
iconSelected: marketIconS,
},
{
key: '/home/fund',
name: 'home.tabFund',
icon: fundIcon,
iconSelected: fundIconS,
},
{
key: '/home/profile',
name: 'home.tabProfile',
icon: profileIcon,
iconSelected: profileIconS,
},
];
const initState: HomeModelState = {
tabItems,
selectedTab: tabItems[0].key,
};
const model: Model = {
namespace: 'home',
state: initState,
effects: {
*changeTab({ payload: selectedTab }, { put, select, all }) {
const {
router: { location: { pathname } },
home: { selectedTab: oldSelectedTab },
} = yield select();
const tasks = [];
if (oldSelectedTab !== selectedTab) {
tasks.push(put({ type: 'onUpdateState', payload: { selectedTab } }));
}
if (pathname !== selectedTab) {
tasks.push(put(routerRedux.replace(selectedTab)));
}
if (tasks.length) {
yield all(tasks);
}
},
*clear(_, { put }) {
yield put({ type: 'onUpdateState', payload: initState })
},
},
reducers: {
onUpdateState(state, { payload }: any) {
return {
...state,
...payload,
};
},
},
subscriptions: {
onRouteChange({ dispatch, history }) {
const { pathname } = history.location;
if (_.some(tabItems, it => it.key === pathname)) {
dispatch({ type: 'changeTab', payload: pathname });
}
}
},
};
export default model;

View File

@@ -0,0 +1,73 @@
import React from 'react';
import FittedImage from 'react-fitted-image';
import { connect, DispatchProp, routerRedux } from 'dva';
import { UserModelState } from '@/models/user';
import SettingItem from '@/components/SettingItem';
import logoAccount from '@/assets/p_account.png';
import logoSystem from '@/assets/p_system.png';
import logoIdentity from '@/assets/p_identity.png';
import logoMsg from '@/assets/p_msg.png';
import logoAbout from '@/assets/p_about.png';
import styles from './style.less';
const ProfilePage: React.FC<UserModelState & DispatchProp> = ({
dispatch,
user,
}) => {
if (!user) return null;
const bannerElement = (
<div className={styles.banner}>
<div className={styles.info}>
<div className="avatar">
{user.avatar ?
<FittedImage src={user.avatar} fit="cover" /> :
<span>{user.nickname ? user.nickname[0] : user.phone[0]}</span>
}
</div>
<span className="name">{user.nickname || user.phone}</span>
</div>
</div>
);
return (
<div className={styles.container}>
{bannerElement}
<div className={styles.content}>
<SettingItem
className="group"
logo={logoAccount}
title="profile.account"
onClick={() => dispatch(routerRedux.push('/account'))}
/>
<SettingItem
className="group"
logo={logoSystem}
title="profile.system"
onClick={() => dispatch(routerRedux.push('/system'))}
/>
<SettingItem
className="group"
logo={logoIdentity}
title="profile.identity"
/>
<SettingItem
className="group"
logo={logoMsg}
title="profile.msg"
/>
<SettingItem
className="group"
logo={logoAbout}
title="profile.about"
/>
</div>
</div>
);
}
export default connect(
({ user }: any) => ({ ...user }),
)(ProfilePage);

View File

@@ -0,0 +1,60 @@
@import '../../../global.less';
.container {
height: 100%;
display: flex;
flex-direction: column;
}
.banner {
width: 100%;
height: 200px;
position: relative;
flex-shrink: 0;
}
.info {
position: absolute;
top: 50%;
left: 0;
width: 100%;
transform: translateY(-55%);
display: flex;
flex-direction: column;
align-items: center;
:global {
.avatar {
@size: 68px;
width: @size;
height: @size;
overflow: hidden;
color: @primary-color;
display: flex;
align-items: center;
justify-content: center;
background-color: @bg-color;
font-size: 2rem;
font-weight: bold;
border-radius: 50%;
border: 2px solid @primary-color;
}
.name {
margin-top: 8px;
font-size: 1.3rem;
}
}
}
.content {
flex-grow: 1;
overflow: auto;
:global {
.group {
margin: 8px;
}
}
}

19
src/pages/home/style.less Normal file
View File

@@ -0,0 +1,19 @@
@import '../../global.less';
@tab-height: 64px;
.home {
height: 100%;
}
.page {
height: 100%;
padding-bottom: @tab-height;
}
.tab {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: @tab-height;
}