diff --git a/package.json b/package.json index 5ab1494..6fa3ee8 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "bignumber.js": "^9.0.0", "dva": "^2.6.0-beta.12", "lodash": "^4.17.15", + "qrcanvas-react": "^2.0.1", "react": "^16.9.0", + "react-clipboard.js": "^2.0.13", "react-dom": "^16.9.0", "react-fitted-image": "^1.0.1", "react-use": "^11.0.0", diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 67c615e..75e006b 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -19,6 +19,7 @@ export default { 'common.cancel': 'Cancel', 'common.ok': 'Ok', 'common.modify': 'Modify', + 'common.copied': '复制成功', 'check.login': 'Sign In', 'check.register': 'Sign Up', @@ -73,6 +74,12 @@ export default { 'estate.actualProfit': '实际收益', 'estate.outTime': '卖出时间', + 'mine.origin': '请选择来源币种', + 'mine.calc': '汇率计算器:', + 'mine.mine': '矿晶', + 'mine.addressTitle': '充值 {label} 到您的专属地址,即可获得矿晶。扫描二维码或点击地址复制:', + 'mine.info': '当你完成充值后,必须回到上一页矿场,点击刷新矿晶列表,平台查账确认后,您才能获赠矿晶。', + 'profile.account': 'Account Settings', 'profile.system': 'System Settings', 'profile.identity': '实名认证', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 3d20d3e..b3fb82c 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -19,6 +19,7 @@ export default { 'common.cancel': '取消', 'common.ok': '确定', 'common.modify': '修改', + 'common.copied': '复制成功', 'check.login': '登录', 'check.register': '注册', @@ -73,6 +74,12 @@ export default { 'estate.actualProfit': '实际收益', 'estate.outTime': '卖出时间', + 'mine.origin': '请选择来源币种', + 'mine.calc': '汇率计算器:', + 'mine.mine': '矿晶', + 'mine.addressTitle': '充值 {label} 到您的专属地址,即可获得矿晶。扫描二维码或点击地址复制:', + 'mine.info': '当你完成充值后,必须回到上一页矿场,点击刷新矿晶列表,平台查账确认后,您才能获赠矿晶。', + 'profile.account': '账户管理', 'profile.system': '系统设置', 'profile.identity': '实名认证', diff --git a/src/models/user.ts b/src/models/user.ts index dabdd99..66ad1c1 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,7 +1,7 @@ import { Model } from 'dva'; import _ from 'lodash'; import { getLocale, setLocale } from 'umi-plugin-locale' -import { loadUser, storeUser, clearUser, clearMineList } from '@/utils/storage'; +import { loadUser, storeUser, clearUser, clearMineList, clearCoins } from '@/utils/storage'; import { User } from '@/types/common'; const initState = () => { @@ -46,6 +46,7 @@ const model: Model = { *logout(_, { put, all }) { clearMineList(); + clearCoins(); yield all([ put({ type: 'clearUser' }), put({ type: 'home/clear' }), diff --git a/src/pages/mine/index.tsx b/src/pages/mine/index.tsx index b92a708..ec7f1c6 100644 --- a/src/pages/mine/index.tsx +++ b/src/pages/mine/index.tsx @@ -3,22 +3,103 @@ * Routes: * - ./src/pages/routes */ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { connect, DispatchProp } from 'dva'; -import { Spin, Tabs } from 'antd'; +import { Spin, Input, Select, message } from 'antd'; import { formatMessage } from 'umi-plugin-locale'; -import FittedImage from 'react-fitted-image'; +import { QRCanvas } from 'qrcanvas-react'; +import Clipboard from 'react-clipboard.js'; import { MineModelState } from './model'; -import { PageState, EstateType } from '@/types/common'; +import { PageState } from '@/types/common'; import Header from '@/components/Header'; -import Info, { InfoItem } from '@/components/Info'; -import Failure from '@/components/Failure'; import styles from './style.less'; +const Option = Select.Option; + const MinePage: React.FC = ({ dispatch, + pageState, + coin, + coins, + srcValue, + dstValue, }) => { + useEffect(() => { + dispatch({ type: 'mine/load' }); + }, []); + + let body; + if (pageState === PageState.Pending) { + body = ( + + ); + } else if (pageState === PageState.Failure) { + body = ( +
{formatMessage({ id: 'common.loadError' })}
+ ); + } else if (pageState === PageState.Success) { + const onAddressCopied = () => { + message.success(formatMessage({ id: 'common.copied' })); + }; + body = ( + <> +
+
+ {formatMessage({ id: 'mine.origin' })} + +
+
+ {formatMessage({ id: 'mine.calc' })} + dispatch({ type: 'mine/changeSrc', payload: e.target.value })} + /> {coin && coin.label.toUpperCase()} = {dstValue} {formatMessage({ id: 'mine.mine' })} +
+
+ +
+
{formatMessage({ id: 'mine.addressTitle' }, { label: coin && coin.label.toUpperCase() })}
+ + + + + {coin && coin.address} + +
+ +
+ {formatMessage({ id: 'mine.info' })} +
+ + ); + } return (
@@ -26,6 +107,10 @@ const MinePage: React.FC = ({ className="header" title={formatMessage({ id: 'home.mine.retrieve' })} /> + +
+
{body}
+
); }; diff --git a/src/pages/mine/model.ts b/src/pages/mine/model.ts index 5e041dc..bd750e7 100644 --- a/src/pages/mine/model.ts +++ b/src/pages/mine/model.ts @@ -1,13 +1,24 @@ import { Model, routerRedux } from "dva"; +import _ from 'lodash'; import { message } from 'antd'; -import { Estate, PageState } from '@/types/common'; -import { getEstateDetail } from './service'; +import { Estate, PageState, CoinUnion } from '@/types/common'; +import { getCoins, calcRate } from './service'; import { isDev } from '@/utils/utils'; export interface MineModelState { + coins: CoinUnion[]; + pageState: PageState; + coin: CoinUnion | null; + srcValue: string; + dstValue: string; } const initState: MineModelState = { + coins: [], + coin: null, + pageState: PageState.Pending, + srcValue: '1', + dstValue: '1', }; const model: Model = { @@ -16,6 +27,46 @@ const model: Model = { state: initState, effects: { + *load(_, { put, call }) { + yield put({ type: 'onUpdateState', pageState: PageState.Pending }); + try { + const coins: CoinUnion[] = yield call(getCoins); + const coin = coins[0]; + yield put({ + type: 'onUpdateState', + pageState: PageState.Success, + coins, + coin: coins[0], + srcValue: '1', + dstValue: calcRate('1', coin && coin.rate), + }); + } catch (e) { + yield put({ + type: 'onUpdateState', + pageState: PageState.Failure, + }); + } + }, + + *selectCoin({ payload: id }, { put, select }) { + const { coins, srcValue } = yield select((state: any) => state.mine); + const coin = _.find(coins, ['id', id]); + yield put({ + type: 'onUpdateState', + coin, + dstValue: calcRate(srcValue, coin && coin.rate), + }); + }, + + *changeSrc({ payload }, { put, select }) { + if (isNaN(payload)) return; + const { coin } = yield select((state: any) => state.mine); + yield put({ + type: 'onUpdateState', + srcValue: payload, + dstValue: calcRate(payload, coin && coin.rate), + }); + }, }, reducers: { diff --git a/src/pages/mine/service.ts b/src/pages/mine/service.ts index b93cb6f..dda58d8 100644 --- a/src/pages/mine/service.ts +++ b/src/pages/mine/service.ts @@ -1,18 +1,42 @@ import http from '@/utils/http'; -import { Estate, EstateType } from '@/types/common'; +import _ from 'lodash'; +import BigNumber from 'bignumber.js'; +import { Estate, EstateType, CoinUnion, CoinSupport, CoinAddress } from '@/types/common'; import { calcEstate } from '@/utils/transform'; +import { loadCoins, storeCoins } from '@/utils/storage'; -export async function getEstateDetail(id: string) { - const { - ok, - data, - msg, - } = await http.get('/estateorder/detail', { id }); - if (ok && data) { - data.type = data.outTime ? EstateType.Sold : EstateType.Hold; - calcEstate(data); - return data; +export async function getCoins(): Promise { + const coinsCached = loadCoins(); + if (coinsCached && coinsCached.length) { + return coinsCached; } - throw Error(msg); + const [ + { ok: sOk, data: sData, msg: sMsg }, + { ok: aOk, data: aData, msg: aMsg }, + ] = await Promise.all([ + http.get('/fund/coins'), + http.get('/fund/queryAddress'), + ]); + + if (!sOk || !sData) { + throw Error(sMsg); + } + + if (!aOk || !aData) { + throw Error(aMsg); + } + + const result = sData.map(it => Object.assign( + it, + _.find(aData, ['id', it.id]), + )); + storeCoins(result); + + return result; +} + +export function calcRate(input: string, rate?: string): string { + if (!input || !rate) return ''; + return new BigNumber(input).times(rate).decimalPlaces(2).toString(); } diff --git a/src/pages/mine/style.less b/src/pages/mine/style.less index e388f9a..f1c0ffe 100644 --- a/src/pages/mine/style.less +++ b/src/pages/mine/style.less @@ -5,6 +5,8 @@ height: 100%; background: linear-gradient(to bottom, @purple-one, @purple-two); position: relative; + display: flex; + flex-direction: column; :global { .header { @@ -13,3 +15,86 @@ } } } + +.body { + flex-grow: 1; + overflow: auto; +} + +.none { + text-align: center; + width: 100%; + margin-top: 40%; +} + +.card { + background-color: #fff; + margin: 10px; + box-shadow: 1px 1px 8px rgba(0, 0, 0, .2); + min-height: 60%; +} + +.section { + @pLeft: 48px; + @pTop: 20px; + padding-left: @pLeft; + padding-right: 30px; + padding-top: @pTop; + position: relative; + + &::before { + content: ''; + display: block; + position: absolute; + @size: 10px; + width: @size; + height: @size; + background-color: @purple-two; + border-radius: @size / 2; + top: @pTop + @size / 2 + 2px; + left: @pLeft / 2 - @size / 2; + } + + :global { + .coin-selection { + display: flex; + align-items: baseline; + + .ant-select { + flex-grow: 1; + margin: 0 4px; + } + } + + .rate-trans { + display: flex; + align-items: baseline; + padding: 4px 8px; + background-color: #eee3f9; + margin: 8px 0; + font-size: 0.8rem; + + .ant-input { + width: 60px; + margin: 0 4px; + } + } + + .address { + font-family: monospace; + word-break: break-all; + } + } +} + +.main { + display: flex; + flex-direction: column; + align-items: center; + + :global { + canvas { + margin: 10px 0; + } + } +} diff --git a/src/types/common.ts b/src/types/common.ts index e3162ce..ca2beba 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -141,6 +141,20 @@ export interface Mine { export type Mines = Mine[]; +export interface CoinSupport { + id: string; + label: string; + rate: string; +} + +export interface CoinAddress { + id: string; + label: string; + address: string; +} + +export interface CoinUnion extends CoinSupport, CoinAddress {}; + export enum MarketEstateAvailable { No = 0, Yes = 1, diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 6c024a9..7b832e5 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -2,6 +2,7 @@ const userKey = 'user-key'; const areaCodeKey = 'area-code-key'; const mineListKey = 'mine-list-key'; +const coinsKey = 'coin-key'; // user info from now on function storeUser(user: any = undefined) { @@ -54,6 +55,23 @@ function clearMineList() { localStorage.removeItem(mineListKey); } +// coin list info from now on +function storeCoins(coins: any) { + localStorage.setItem(coinsKey, JSON.stringify(coins)); +} + +function loadCoins() { + try { + return JSON.parse(localStorage.getItem(coinsKey) || 'null'); + } catch (e) { + return null; + } +} + +function clearCoins() { + localStorage.removeItem(coinsKey); +} + export { storeUser, loadUser, @@ -65,4 +83,8 @@ export { storeMineList, loadMineList, clearMineList, + + storeCoins, + loadCoins, + clearCoins, } diff --git a/yarn.lock b/yarn.lock index 716933c..3514eaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1055,7 +1055,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== @@ -1421,6 +1421,11 @@ dependencies: "@types/node" "*" +"@types/clipboard@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/clipboard/-/clipboard-2.0.1.tgz#75a74086c293d75b12bc93ff13bc7797fef05a40" + integrity sha512-gJJX9Jjdt3bIAePQRRjYWG20dIhAgEqonguyHxXuqALxsoDsDLimihqrSg8fXgVTJ4KZCzkfglKtwsh/8dLfbA== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -1599,7 +1604,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.9.2", "@types/react@~16.9.1": +"@types/react@*", "@types/react@^16.8.17", "@types/react@^16.9.2", "@types/react@~16.9.1": version "16.9.2" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.2.tgz#6d1765431a1ad1877979013906731aae373de268" integrity sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg== @@ -3631,6 +3636,15 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +clipboard@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" + integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + clipboardy@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.1.0.tgz#0123a0c8fac92f256dc56335e0bb8be97a4909a5" @@ -4641,6 +4655,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -6747,6 +6766,13 @@ gonzales-pe@^4.2.3: dependencies: minimist "1.1.x" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + got@9.6.0, got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -11636,6 +11662,28 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= +qrcanvas-react@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/qrcanvas-react/-/qrcanvas-react-2.0.1.tgz#4cf8c0d0c8cb9c075bbc1fbd19d6bdf1e9919e46" + integrity sha512-mwcd9lcEXzFsMvrV/eKxYxCUdlv7gin8L0LZJ5R0nNXPLFndz1LAHa3dtPIvfBzwY6RhQoh1N74+ODZvEt4qJQ== + dependencies: + "@babel/runtime" "^7.4.4" + "@types/react" "^16.8.17" + qrcanvas "^3.0.4" + +qrcanvas@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/qrcanvas/-/qrcanvas-3.0.4.tgz#f0fb400f31f148508f494fa49a29aa5b276d2dfe" + integrity sha512-aAafqeNTJVsWIc2vbqw/R+ByUUdmcncTpiy7eRq9vbcvzuhQZcK4IdfjVc4ey7nwBCncnraeYRZbnOecd1rDmw== + dependencies: + "@babel/runtime" "^7.4.4" + qrcode-generator "^1.4.3" + +qrcode-generator@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.3.tgz#4876e8f280e65b6c94615f4c19c484f6b964b199" + integrity sha512-++rVRvMRq5BlHfmAafl8a4ppUntzUxCCUTT2t0siUgqKwdnqRzY8IH6f6WSX5dZUhD2Ul5/MIKuTJddflwrGzw== + qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" @@ -12243,6 +12291,15 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-clipboard.js@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-2.0.13.tgz#4430d4ef989c4c39303553c2c22f20b72b2d73dd" + integrity sha512-1tjFU52LbtETZe223bc/q9JnbguWXqrgWCHpnVWJ4YYhzoV62jJP0NghAYH6+F7G5SI80wnQHdBjgqppNTY7Xg== + dependencies: + "@types/clipboard" "^2.0.1" + clipboard "^2.0.0" + prop-types "^15.5.0" + react-dev-utils@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.0.1.tgz#5c03d85a0b2537d0c46af7165c24a7dfb274bef2" @@ -13367,6 +13424,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + selfsigned@^1.9.1: version "1.10.4" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd" @@ -14586,6 +14648,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tiny-invariant@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73"