diff --git a/src/global.less b/src/global.less index 49a91d5..98bee40 100644 --- a/src/global.less +++ b/src/global.less @@ -13,6 +13,9 @@ body { @bg-primary-color: #ffded8; @hint-color: gray; +@purple-one: #c17ffe; +@purple-two: #7483f6; + @header-height: 54px; .ant-btn { diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 2be23a8..67c615e 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -14,9 +14,11 @@ export default { 'common.releaseToRefresh': 'Release to refresh', 'common.refreshing': 'Refreshing', 'common.loading': 'Loading', + 'common.loadError': 'Load Failed', 'common.loadFinished': 'Finished', 'common.cancel': 'Cancel', 'common.ok': 'Ok', + 'common.modify': 'Modify', 'check.login': 'Sign In', 'check.register': 'Sign Up', @@ -46,15 +48,17 @@ export default { 'home.home.statistic': 'Total', 'home.home.header': '精选市场', - 'home.estate.hold': 'Hold', - 'home.estate.sold': 'Sold', - 'home.estate.holdTitle': 'Hold amount: {num}', - 'home.estate.soldTitle': 'Sold amount: {num}', - 'home.estate.timeDue': 'Due {time}', - 'home.estate.timeSold': 'Sold {time}', - 'home.estate.cost': 'Cost', - 'home.estate.profit': 'Profit', - 'home.estate.profit2': 'Profit: ', + 'home.mine.lastday': '昨日挖矿收益', + 'home.mine.acc': '累计挖矿收益:', + 'home.mine.valid': '当前有效矿晶:', + 'home.mine.retrieve': '获取矿晶', + 'home.mine.refresh': 'refresh', + 'home.mine.validTime': '获赠日期:', + 'home.mine.invalidTime': '有效期至:', + 'home.mine.status': 'status: ', + 'home.mine.status0': 'Pending', + 'home.mine.status1': '正在挖矿', + 'home.mine.status2': '已耗尽能源', 'estate.tab1': '买卖记录', 'estate.tab2': '地块介绍', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 3148618..3d20d3e 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -14,9 +14,11 @@ export default { 'common.releaseToRefresh': '释放以刷新', 'common.refreshing': '刷新中', 'common.loading': '加载中', + 'common.loadError': '加载失败', 'common.loadFinished': '已经到底了~', 'common.cancel': '取消', 'common.ok': '确定', + 'common.modify': '修改', 'check.login': '登录', 'check.register': '注册', @@ -46,15 +48,17 @@ export default { 'home.home.statistic': '累计收益', 'home.home.header': '精选市场', - 'home.estate.hold': '当前持有', - 'home.estate.sold': '已经卖出', - 'home.estate.holdTitle': '当前持有地产数:{num}', - 'home.estate.soldTitle': '已经卖出地产数:{num}', - 'home.estate.timeDue': '{time} 将到期', - 'home.estate.timeSold': '{time} 已卖出', - 'home.estate.cost': '成本', - 'home.estate.profit': '收益', - 'home.estate.profit2': '预期收益:', + 'home.mine.lastday': '昨日挖矿收益', + 'home.mine.acc': '累计挖矿收益:', + 'home.mine.valid': '当前有效矿晶:', + 'home.mine.retrieve': '获取矿晶', + 'home.mine.refresh': '刷新', + 'home.mine.validTime': '获赠日期:', + 'home.mine.invalidTime': '有效期至:', + 'home.mine.status': '状态:', + 'home.mine.status0': '等待确认', + 'home.mine.status1': '正在挖矿', + 'home.mine.status2': '已耗尽能源', 'estate.tab1': '买卖记录', 'estate.tab2': '地块介绍', diff --git a/src/pages/account/index.tsx b/src/pages/account/index.tsx index acc4765..43a6c7a 100644 --- a/src/pages/account/index.tsx +++ b/src/pages/account/index.tsx @@ -6,16 +6,20 @@ import React from 'react'; import { connect, DispatchProp } from 'dva'; import { formatMessage } from 'umi-plugin-locale'; -import { Button, Modal } from 'antd'; +import { Button, Modal, Input } from 'antd'; import Header from '@/components/Header'; import AccountItem from '@/components/AccountItem'; import { UserModelState } from '@/models/user'; import styles from './style.less'; +import { AccountModelState } from './model'; -const AccountPage: React.FC = ({ +const AccountPage: React.FC = ({ dispatch, user, + nameEditable, + nameInput, + nameSubmitting, }) => { const onLogout = () => { @@ -28,6 +32,25 @@ const AccountPage: React.FC = ({ }); }; + const editNameModal = ( + dispatch({ type: 'account/onUpdateState', nameEditable: false })} + onOk={() => dispatch({ type: 'account/changeName' })} + okText={formatMessage({ id: 'common.ok' })} + cancelText={formatMessage({ id: 'common.cancel' })} + confirmLoading={nameSubmitting} + > + dispatch({ type: 'account/onUpdateState', nameInput: e.target.value })} + /> + + ); + return (
= ({ title="account.nickname" message={user.nickname} showGo={false} + onClick={() => dispatch({ type: 'account/onUpdateState', nameEditable: true })} /> = ({ {formatMessage({ id: 'account.logout' })}
} + {editNameModal} ); }; export default connect( - ({ user }: any) => ({ ...user }), + ({ user, account }: any) => ({ ...user, ...account }), )(AccountPage); diff --git a/src/pages/account/model.ts b/src/pages/account/model.ts new file mode 100644 index 0000000..911119f --- /dev/null +++ b/src/pages/account/model.ts @@ -0,0 +1,50 @@ +import { Model } from 'dva'; +import { changeName } from './service'; +import { message } from 'antd'; + +export interface AccountModelState { + nameEditable: boolean; + nameInput: string; + nameSubmitting: boolean; +} + +const model: Model = { + namespace: 'account', + + state: { + nameEditable: false, + nameInput: '', + nameSubmitting: false, + } as AccountModelState, + + effects: { + *changeName(_, { put, all, call, select }) { + const { nameInput } = yield select((state: any) => state.account); + if (!nameInput) return; + + yield put({ type: 'onUpdateState', nameSubmitting: true }); + try { + yield call(changeName, nameInput); + yield all([ + put({ type: 'onUpdateState', nameInput: '', nameEditable: false }), + put({ type: 'user/updateUser', payload: { nickname: nameInput } }), + ]); + } catch (e) { + message.error(e.message); + } finally { + yield put({ type: 'onUpdateState', nameSubmitting: false }); + } + }, + }, + + reducers: { + onUpdateState(state, payload) { + return { + ...state, + ...payload, + }; + }, + }, +}; + +export default model; diff --git a/src/pages/account/service.ts b/src/pages/account/service.ts new file mode 100644 index 0000000..d3b482b --- /dev/null +++ b/src/pages/account/service.ts @@ -0,0 +1,9 @@ +import http from '@/utils/http'; + +export async function changeName(nickname: string) { + const { ok, msg } = await http.post('/user/chnickname', { nickname }); + if (ok) { + return true; + } + throw Error(msg); +} diff --git a/src/pages/home/estate/index.tsx b/src/pages/home/estate/index.tsx index 262515c..a9d8aec 100644 --- a/src/pages/home/estate/index.tsx +++ b/src/pages/home/estate/index.tsx @@ -1,103 +1,103 @@ import React, { useEffect } from 'react'; -import { Radio } from 'antd'; -import FittedImage from 'react-fitted-image'; import { connect, DispatchProp } from 'dva'; +import { Spin, Icon } from 'antd'; 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 { HomeMineModelState } from './model'; +import { MineStatus, Mine } 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 ( -
dispatch({ type: 'homeEstate/gotoDetail', payload: data })} - > -
- -
-
- {data.name} - - { - holdTab ? - formatMessage({ id: 'home.estate.timeDue' }, { time: data.lockTime }) : - formatMessage({ id: 'home.estate.timeSold' }, { time: data.outTime }) - } - -
-
- - {formatMessage({ id: 'home.estate.cost' })} {data.inPrice} LOG - - {!holdTab && - {formatMessage({ id: 'home.estate.profit' })} {data.profitPrice} LOG - } - {holdTab && - {formatMessage({ id: 'home.estate.profit2' })}{data.profitShow} - } -
-
- ); -}; - -const EstatePage: React.FC = ({ +const MinePage: React.FC = ({ dispatch, - loading, - refreshing, - tab, + refreshingList, + errorList, data, - num, + refreshingBasic, + errorBasic, + info, }) => { + useEffect(() => { - if (!data.length) { - dispatch({ type: 'homeEstate/refresh' }); + if (!data || errorList) { + dispatch({ type: 'homeMine/loadList' }); + } + if (!info || errorBasic) { + dispatch({ type: 'homeMine/loadBasic' }); } }, []); + const onRefresh = () => { + if (refreshingBasic || refreshingList) return; + dispatch({ type: 'homeMine/loadList', payload: true }); + dispatch({ type: 'homeMine/loadBasic' }); + }; + + const renderBanner = ( +
+
+ {formatMessage({ id: 'home.mine.lastday' })} + + +{info ? info.revenueLastday : 0} + VIC + +
+
+ + {formatMessage({ id: 'home.mine.acc' })} + {info ? info.revenueAll : 0} VIC + + + {formatMessage({ id: 'home.mine.valid' })} + {info ? info.stoneAll : 0} KG + +
+ +
+ {formatMessage({ id: 'home.mine.retrieve' })} +
+
+
+ ); + + const renderItem = (it: Mine) => ( +
+
+
{formatMessage({ id: 'home.mine.validTime' })}{it.validTime}
+
+{it.amount} KG
+
+
+
+ {formatMessage({ id: 'home.mine.status' })} + {formatMessage({ id: `home.mine.status${it.status}` })} +
+
{formatMessage({ id: 'home.mine.invalidTime' })}{it.invalidTime}
+
+
+ ); + return (
-
- dispatch({ type: 'homeEstate/changeTab', payload: e.target.value })} - > - {formatMessage({ id: 'home.estate.hold' })} - {formatMessage({ id: 'home.estate.sold' })} - + {renderBanner} + +
+
+ + {formatMessage({ id: 'home.mine.refresh' })} +
-
); }; export default connect( - ({ homeEstate } : any) => ({ ...homeEstate }), -)(EstatePage); + ({ homeMine } : any) => ({ ...homeMine }), +)(MinePage); diff --git a/src/pages/home/estate/model.ts b/src/pages/home/estate/model.ts index 63756f0..c13b6ae 100644 --- a/src/pages/home/estate/model.ts +++ b/src/pages/home/estate/model.ts @@ -1,104 +1,88 @@ import { Model, routerRedux } from 'dva'; import { message } from 'antd'; -import { EstateType, Estates } from '@/types/common'; -import { getEstateList, EstateList } from './service'; +import { Mines, MineBasic } from '@/types/common'; +import { getMineList, getMineBasic } from './service'; -export interface HomeEstateModelState { - refreshing: boolean; - loading: boolean; - loadingFinished: boolean; - data: Estates; - num: number; - pageIndex: number; +export interface HomeMineModelState { + refreshingList: boolean; + errorList: boolean; + data: Mines | null; - tab: EstateType; + refreshingBasic: boolean; + errorBasic: boolean; + info: MineBasic | null; } -const EstateModel: Model = { - namespace: 'homeEstate', +const MineModel: Model = { + namespace: 'homeMine', state: { - refreshing: false, - loading: false, - enableLoad: true, - loadingFinished: false, - data: [], - num: 0, - pageIndex: 1, - pageSize: 10, - tab: EstateType.Hold, - } as HomeEstateModelState, + refreshingList: false, + errorList: false, + data: null, + + refreshingBasic: false, + errorBasic: false, + info: null, + } as HomeMineModelState, effects: { - *refresh(_, { put, call, select }) { - const { - pageSize, - tab, - } = yield select((state: any) => state.homeEstate); - yield put({ type: 'onUpdateState', payload: { refreshing: true, loading: false } }); + *loadList({ payload: refresh }, { put, call, select }) { + const { refreshingList } = yield select((state: any) => state.homeMine); + if (refreshingList) return; + + yield put({ + type: 'onUpdateState', + refreshingList: true, + errorList: 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, - }, - }); - } + const data = yield call(getMineList, refresh); + yield put({ + type: 'onUpdateState', + data, + refreshingList: false, + errorList: false, + }); } catch (e) { - message.error(e.message); - yield put({ type: 'onUpdateState', payload: { refreshing: false, pageIndex: 1 } }); + yield put({ + type: 'onUpdateState', + refreshingList: false, + errorList: true, + data: null, + }); } }, - *loadMore(_, { put, call, select }) { - const { - pageIndex, - pageSize, - tab, - data, - } = yield select((state: any) => state.homeEstate); - yield put({ type: 'onUpdateState', payload: { loading: true, refreshing: false } }); + *loadBasic(_, { put, call, select }) { + const { refreshingBasic } = yield select((state: any) => state.homeMine); + if (refreshingBasic) return; + + yield put({ + type: 'onUpdateState', + refreshingBasic: true, + errorBasic: 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, - }, - }); - } + const info = yield call(getMineBasic); + yield put({ + type: 'onUpdateState', + info, + refreshingBasic: false, + errorBasic: false, + }); } catch (e) { - message.error(e.message); - yield put({ type: 'onUpdateState', payload: { loading: false } }); + yield put({ + type: 'onUpdateState', + refreshingBasic: false, + errorBasic: true, + }); } }, - - *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) { + onUpdateState(state, payload) { return { ...state, ...payload, @@ -107,4 +91,4 @@ const EstateModel: Model = { }, }; -export default EstateModel; +export default MineModel; diff --git a/src/pages/home/estate/service.ts b/src/pages/home/estate/service.ts index e6b045e..3299368 100644 --- a/src/pages/home/estate/service.ts +++ b/src/pages/home/estate/service.ts @@ -1,21 +1,34 @@ import http from '@/utils/http'; -import { Estates, EstateType } from '@/types/common'; -import { calcEstate } from '@/utils/transform'; +import { Mines, MineBasic } from '@/types/common'; +import { loadMineList, storeMineList } from '@/utils/storage'; -export interface EstateList { - total: number; - list: Estates; -} - -export async function getEstateList(pageIndex: number, pageSize: number, type: EstateType) { +export async function getMineBasic(): Promise { const { ok, data, msg, - } = await http.get('/estateorder/list', { type, pageIndex, pageSize }); + } = await http.get('/mine/basic'); if (ok && data) { - data.list.forEach(it => it.type = type); - data.list.forEach(calcEstate); + return data; + } + throw Error(msg); +} + +export async function getMineList(refresh = false): Promise { + if (!refresh) { + const dataCached = loadMineList(); + if (dataCached && dataCached.length) { + return dataCached; + } + } + + const { + ok, + data, + msg, + } = await http.get('/mine/list'); + if (ok && data) { + storeMineList(data); return data; } throw Error(msg); diff --git a/src/pages/home/estate/style.less b/src/pages/home/estate/style.less index 7f61214..d8ec6fb 100644 --- a/src/pages/home/estate/style.less +++ b/src/pages/home/estate/style.less @@ -7,90 +7,157 @@ 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; + .ant-spin-nested-loading { + flex-grow: 1; overflow: hidden; - border-radius: 4px; + } + + .ant-spin-container { + height: 100%; + overflow: auto; + } + } +} + +.message { + text-align: center; + margin-top: 20px; +} + +.banner { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + + :global { + .bg { + z-index: -1; + position: absolute; + left: 0; + right: 0; + width: 100%; + height: 90%; + background: linear-gradient(to bottom, @purple-one, @purple-two); + } + + .title, .content { + color: white; + font-weight: lighter; + } + + .title { + margin-top: 40px; } .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; - } + margin: 10px 0 24px; } - .postfix { - flex-shrink: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-end; - font-size: 0.8rem; - font-weight: lighter; + .content .large { + font-size: 1.6rem; + margin-right: 6px; + font-weight: normal; + } + } - .cost-msg { - margin-bottom: 6px; + .card { + align-self: stretch; + background-color: white; + padding: 20px; + margin: 0 10px; + box-shadow: 1px 1px 8px rgba(0,0,0,.1); + display: flex; + align-items: center; - &.primary { - color: @primary-color; - } - - > span { - font-size: 1.2rem; - } + :global { + .left { + flex-grow: 1; + display: flex; + flex-direction: column; } - .hold-msg { - background-color: @bg-primary-color; - color: @primary-color; + .right { + flex-shrink: 0; + margin-left: 8px; } - .sold-msg { - color: @primary-color; + .line { + margin: 4px 0; + } + + .title2 { + font-weight: lighter; + } + + .app-btn { + user-select: none; + white-space: nowrap; + background-color: @purple-one; + @btn-height: 28px; + height: @btn-height; + line-height: @btn-height; + padding: 0 24px; + border-radius: @btn-height / 2; + color: white; + font-size: 0.8rem; + box-shadow: 1px 1px 8px rgba(0,0,0,.2); + transition: background-color .18s; + + &:active { + background-color: lighten(@purple-one, 10%); + } } } } } + +.controller { + background-color: @bg-color; + margin: 10px; + text-align: right; + + :global { + .desc { + display: inline-block; + font-weight: lighter; + font-size: 0.8rem; + margin-left: 4px; + } + + .content { + padding: 4px 10px; + } + } +} + +.item { + margin: 4px 10px; + display: flex; + flex-direction: column; + border: 1px solid @bg-color; + padding: 8px 10px; + font-size: 0.9rem; + line-height: 1.8; + + :global { + .line { + display: flex; + justify-content: space-between; + align-items: center; + } + + .amount { + color: @purple-one; + font-size: 1rem; + } + + .small { + font-weight: lighter; + font-size: 0.8rem; + } + } +} diff --git a/src/types/common.ts b/src/types/common.ts index cdb16ba..e3162ce 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -92,6 +92,55 @@ export interface Estate { export type Estates = Estate[]; +export interface MineBasic { + /** + * 昨日收益 + */ + revenueLastday: string; + + /** + * 总收益 + */ + revenueAll: string; + + /** + * 当前有效矿晶 + */ + stoneAll: string; +} + +export enum MineStatus { + Pending = 0, // 等待确认 + Working = 1, // 正在挖矿 + RunOut = 2, // 耗尽能源 +} + +/** + * 矿 + */ +export interface Mine { + id: string; + + amount: string; + + /** + * 获赠日期 + */ + validTime: string; + + /** + * 到期日期 + */ + invalidTime: string; + + /** + * 矿晶状态 + */ + status: MineStatus; +} + +export type Mines = Mine[]; + export enum MarketEstateAvailable { No = 0, Yes = 1, diff --git a/src/utils/http.ts b/src/utils/http.ts index d7372c4..03c2380 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -15,8 +15,8 @@ const successLabel = 'color:white;background-color:green;padding: 1px 2px'; const failureLabel = 'color:white;background-color:red;padding: 1px 2px'; const originLabel = 'color:unset;background-color:unset;padding:unset'; -const baseURL = 'https://server.fiv.tacever.org/v1'; -// const baseURL = '//yapi.faronear.org/mock/20/v1'; +// const baseURL = 'https://server.fiv.tacever.org/v1'; +const baseURL = '//yapi.faronear.org/mock/20/v1'; const instance = axios.create({ baseURL, }); diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 43c0685..89ccc69 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,7 +1,9 @@ const userKey = 'user-key'; const areaCodeKey = 'area-code-key'; +const mineListKey = 'mine-list-key'; +// user info from now on function storeUser(user: any = undefined) { if (user === undefined) { localStorage.removeItem(userKey); @@ -20,6 +22,8 @@ function loadUser() { const clearUser = storeUser; + +// area code info from now on function storeAreaCode(codes: any) { localStorage.setItem(areaCodeKey, JSON.stringify(codes)); } @@ -32,6 +36,20 @@ function loadAreaCode() { } } + +// mine list info from now on +function storeMineList(list: any) { + localStorage.setItem(mineListKey, JSON.stringify(list)); +} + +function loadMineList() { + try { + return JSON.parse(localStorage.getItem(mineListKey) || 'null'); + } catch (e) { + return null; + } +} + export { storeUser, loadUser, @@ -39,4 +57,7 @@ export { storeAreaCode, loadAreaCode, + + storeMineList, + loadMineList, }