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

16
.editorconfig Executable file
View File

@@ -0,0 +1,16 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

2
.env Normal file
View File

@@ -0,0 +1,2 @@
BROWSER=none
ESLINT=1

3
.eslintrc Executable file
View File

@@ -0,0 +1,3 @@
{
"extends": "eslint-config-umi"
}

22
.gitignore vendored Executable file
View File

@@ -0,0 +1,22 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/package-lock.json
# production
/dist
# misc
.DS_Store
# umi
.umi
.umi-production
# IDE
/.vscode
/.idea
*.iml

7
.prettierignore Normal file
View File

@@ -0,0 +1,7 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production

11
.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

43
.umirc.ts Normal file
View File

@@ -0,0 +1,43 @@
import { IConfig } from 'umi-types';
const isDev = process.env.NODE_ENV === 'development';
// ref: https://umijs.org/config/
const config: IConfig = {
history: isDev ? undefined : 'hash',
hash: true,
publicPath: './',
treeShaking: true,
theme: {
'primary-color': '#3A3A38',
},
plugins: [
// ref: https://umijs.org/plugin/umi-plugin-react.html
['umi-plugin-react', {
antd: true,
dva: true,
dynamicImport: {
webpackChunkName: true,
loadingComponent: './components/PageLoading/index',
},
title: 'fiv',
dll: true,
locale: {
enable: true,
baseNavigator: true,
default: 'en-US',
},
routes: {
exclude: [
/models\//,
/services\//,
/model\.(t|j)sx?$/,
/service\.(t|j)sx?$/,
/components\//,
],
},
}],
],
}
export default config;

15
jsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"jsx": "preserve"
},
"exclude": [
"dist",
"node_modules"
]
}

0
mock/.gitkeep Normal file
View File

64
package.json Normal file
View File

@@ -0,0 +1,64 @@
{
"private": true,
"name": "fiv",
"scripts": {
"start": "umi dev",
"build": "umi build",
"test": "umi test",
"lint:es": "eslint --ext .js src mock tests",
"lint:ts": "tslint \"src/**/*.ts\" \"src/**/*.tsx\""
},
"dependencies": {
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.1",
"antd": "^3.22.0",
"axios": "^0.19.0",
"bignumber.js": "^9.0.0",
"dva": "^2.6.0-beta.12",
"lodash": "^4.17.15",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-fitted-image": "^1.0.1",
"react-use": "^11.0.0",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"react-window-infinite-loader": "^1.0.3",
"umi-plugin": "^0.1.2"
},
"devDependencies": {
"@types/jest": "^24.0.18",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"@types/react-test-renderer": "^16.0.3",
"babel-eslint": "^10.0.2",
"eslint": "^6.2.1",
"eslint-config-umi": "^1.5.0",
"eslint-plugin-flowtype": "^4.2.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.11.1",
"husky": "^3.0.4",
"lint-staged": "^9.2.3",
"react-test-renderer": "^16.7.0",
"tslint": "^5.19.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-react": "^4.0.0",
"umi": "^2.8.20",
"umi-plugin-react": "^1.9.19",
"umi-types": "^0.3.17"
},
"lint-staged": {
"*.{ts,tsx}": [
"tslint --fix",
"git add"
]
},
"engines": {
"node": ">=8.0.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

10
src/app.ts Normal file
View File

@@ -0,0 +1,10 @@
export const dva = {
config: {
onError(err: ErrorEvent) {
err.preventDefault();
// tslint:disable-next-line: no-console
console.error(err.message);
},
},
};

BIN
src/assets/bg_login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

16
src/assets/estate.svg Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 768 768" enable-background="new 0 0 768 768" xml:space="preserve">
<path fill="#CDCDCD" d="M260.2,249.3h75l-46.1-41.7l84.2-93.2l96,86.8l40.3,35.1l64.7-71.6l93.6,84.6h64.9c-2.5-7.9-7-15.1-13-20.8
L607.9,127.4c-20.6-18.5-52.3-16.9-70.9,3.6l-31.9,35.2c-0.3-0.2-0.4-0.7-0.7-0.9L407,77.1c-20.7-18.5-52.3-16.8-71,3.7l-84.2,93.2
c-9,9.9-13.6,22.9-12.9,36.2c0.7,13.4,6.5,25.8,16.5,34.8L260.2,249.3z M260.2,249.3"/>
<path fill="#CDCDCD" d="M179.1,274.4l-50.2-150.7H35.4c-13.9,0-25.1,11.2-25.1,25.1s11.2,25.1,25.1,25.1h57.3l150.7,452.2h70.9
c3.9-18.3,11.8-35.4,23.2-50.2h-57.9l-83.7-251.2h483l-83.8,251.2h-57.9c11.1,14.7,19.4,31.6,23.2,50.2h70.9l117.2-351.7H179.1z
M179.1,274.4"/>
<rect x="10.4" y="63.9" fill="none" width="738.9" height="688.8"/>
<path fill="#CDCDCD" d="M437.3,550.8c-55.4,0-100.5,45.1-100.5,100.5c0,55.4,45.1,100.5,100.5,100.5s100.5-45.1,100.5-100.5
C537.8,595.8,492.8,550.8,437.3,550.8L437.3,550.8z M437.3,701.5c-26.9-1.2-48-23.3-48-50.2c0-26.9,21.2-49,48-50.2
c27.8,0,50.2,22.5,50.2,50.2S465.1,701.5,437.3,701.5L437.3,701.5z M437.3,701.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

17
src/assets/estate_s.svg Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 768 768" enable-background="new 0 0 768 768" xml:space="preserve">
<polygon fill="#E5E5E5" points="190.5,346.5 615,346.5 528,583.5 261,583.5 "/>
<path d="M260.2,249.3h75l-46.1-41.7l84.2-93.2l96,86.8l40.3,35.1l64.7-71.6l93.6,84.6h64.9c-2.5-7.9-7-15.1-13-20.8L607.9,127.4
c-20.6-18.5-52.3-16.9-70.9,3.6l-31.9,35.2c-0.3-0.2-0.4-0.7-0.7-0.9L407,77.1c-20.7-18.5-52.3-16.8-71,3.7l-84.2,93.2
c-9,9.9-13.6,22.9-12.9,36.2c0.7,13.4,6.5,25.8,16.5,34.8L260.2,249.3z M260.2,249.3"/>
<path d="M179.1,274.4l-50.2-150.7H35.4c-13.9,0-25.1,11.2-25.1,25.1s11.2,25.1,25.1,25.1h57.3l150.7,452.2h70.9
c3.9-18.3,11.8-35.4,23.2-50.2h-57.9l-83.7-251.2h483l-83.8,251.2h-57.9c11.1,14.7,19.4,31.6,23.2,50.2h70.9l117.2-351.7H179.1z
M179.1,274.4"/>
<rect x="10.4" y="63.9" fill="none" width="738.9" height="688.8"/>
<path d="M437.3,550.8c-55.4,0-100.5,45.1-100.5,100.5c0,55.4,45.1,100.5,100.5,100.5s100.5-45.1,100.5-100.5
C537.8,595.8,492.8,550.8,437.3,550.8L437.3,550.8z M437.3,701.5c-26.9-1.2-48-23.3-48-50.2c0-26.9,21.2-49,48-50.2
c27.8,0,50.2,22.5,50.2,50.2S465.1,701.5,437.3,701.5L437.3,701.5z M437.3,701.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

193
src/assets/f_charge.svg Normal file
View File

@@ -0,0 +1,193 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 761.5 764.2" enable-background="new 0 0 761.5 764.2" xml:space="preserve">
<g>
<defs>
<path id="SVGID_1_" d="M742.3,341.5v42.2c0,2.9,2.3,5.3,5.3,5.3c2.9,0,5.3-2.3,5.3-5.3v-42.2c0-2.9-2.3-5.3-5.3-5.3
C744.6,336.2,742.3,338.6,742.3,341.5"/>
</defs>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="742.2673" y1="362.5726" x2="752.8118" y2="362.5726">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_1_" overflow="visible" fill="url(#SVGID_2_)"/>
<clipPath id="SVGID_3_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-169.8742" y1="488.795" x2="-165.9059" y2="488.795" gradientTransform="matrix(2.6571 0 0 -2.6571 1193.6409 1661.3522)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="742.3" y="336.2" clip-path="url(#SVGID_3_)" fill="url(#SVGID_4_)" width="10.5" height="52.7"/>
</g>
<g>
<defs>
<path id="SVGID_5_" d="M634.9,92.5c14.5,0,26.2,13.7,26.2,31.1c0-16.9,11.7-31.1,26.2-31.1c-14.5,0-26.2-13.7-26.2-31.1
C661.1,78.4,649.4,92.5,634.9,92.5"/>
</defs>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="634.853" y1="92.4785" x2="687.3016" y2="92.4785">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_5_" overflow="visible" fill="url(#SVGID_6_)"/>
<clipPath id="SVGID_7_">
<use xlink:href="#SVGID_5_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="-362.1954" y1="503.75" x2="-358.2255" y2="503.75" gradientTransform="matrix(13.2113 0 0 -13.2113 5419.9243 6747.6699)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="634.9" y="61.4" clip-path="url(#SVGID_7_)" fill="url(#SVGID_8_)" width="52.4" height="62.1"/>
</g>
<g>
<defs>
<path id="SVGID_9_" d="M217.4,23.8c0-7,5.9-12.9,12.9-12.9c7,0,12.9,5.9,12.9,12.9c0,7-5.9,12.9-12.9,12.9
C223.3,36.7,217.4,30.9,217.4,23.8 M230.3,1c-12.3,0-22.8,10-22.8,22.8c0,12.3,10,22.9,22.8,22.9s22.9-10,22.9-22.9
C253.1,11.5,243.2,1,230.3,1z"/>
</defs>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="207.4467" y1="23.8498" x2="253.1462" y2="23.8498">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_9_" overflow="visible" fill="url(#SVGID_10_)"/>
<clipPath id="SVGID_11_">
<use xlink:href="#SVGID_9_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="-383.9455" y1="510.4826" x2="-379.9753" y2="510.4826" gradientTransform="matrix(11.5108 0 0 -11.5108 4626.9512 5899.8926)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="207.4" y="1" clip-path="url(#SVGID_11_)" fill="url(#SVGID_12_)" width="45.7" height="45.7"/>
</g>
<g>
<defs>
<path id="SVGID_13_" d="M711.8,159.4c0-7,5.9-12.9,12.9-12.9c7,0,12.9,5.9,12.9,12.9c0,7-5.9,12.9-12.9,12.9
C717.7,172.2,711.8,166.4,711.8,159.4 M701.8,159.4c0,12.3,10,22.9,22.8,22.9s22.9-10,22.9-22.9c0-12.3-10-22.8-22.9-22.8
C712.4,136.5,701.8,146.5,701.8,159.4"/>
</defs>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="701.8401" y1="159.3624" x2="747.5396" y2="159.3624">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_13_" overflow="visible" fill="url(#SVGID_14_)"/>
<clipPath id="SVGID_15_">
<use xlink:href="#SVGID_13_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="-352.1799" y1="501.9952" x2="-348.2098" y2="501.9952" gradientTransform="matrix(11.5108 0 0 -11.5108 4755.6987 5937.7085)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="701.8" y="136.5" clip-path="url(#SVGID_15_)" fill="url(#SVGID_16_)" width="45.7" height="45.7"/>
</g>
<g>
<defs>
<path id="SVGID_17_" d="M143.3,173.8c0,7.8,6.3,14.4,14.4,14.4c8.1,0,14.4-6.3,14.4-14.4c0-7.8-6.3-14.4-14.4-14.4
C150,159.4,143.3,165.7,143.3,173.8"/>
</defs>
<linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="143.3141" y1="173.7856" x2="172.1528" y2="173.7856">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_17_" overflow="visible" fill="url(#SVGID_18_)"/>
<clipPath id="SVGID_19_">
<use xlink:href="#SVGID_17_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_20_" gradientUnits="userSpaceOnUse" x1="-379.4139" y1="507.9658" x2="-375.4444" y2="507.9658" gradientTransform="matrix(7.265 0 0 -7.265 2899.7617 3864.1646)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="143.3" y="159.4" clip-path="url(#SVGID_19_)" fill="url(#SVGID_20_)" width="28.8" height="28.8"/>
</g>
<g>
<defs>
<path id="SVGID_21_" d="M3.1,91.8c35.1,0,63.3,33.4,63.3,75c0-41.6,28.1-75,63.3-75c-35.2,0-63.3-33.4-63.3-75
C66.3,57.8,38.2,91.8,3.1,91.8"/>
</defs>
<linearGradient id="SVGID_22_" gradientUnits="userSpaceOnUse" x1="3.0593" y1="91.7778" x2="129.6054" y2="91.7778">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_21_" overflow="visible" fill="url(#SVGID_22_)"/>
<clipPath id="SVGID_23_">
<use xlink:href="#SVGID_21_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_24_" gradientUnits="userSpaceOnUse" x1="-400.916" y1="494.761" x2="-396.946" y2="494.761" gradientTransform="matrix(31.8752 0 0 -31.8752 12782.3242 15862.3652)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="3.1" y="16.8" clip-path="url(#SVGID_23_)" fill="url(#SVGID_24_)" width="126.5" height="150"/>
</g>
<g>
<defs>
<path id="SVGID_25_" d="M28.9,291.1c0-5.3,4.7-10,10-10c5.9,0,10,4.1,10,10c0,5.3-4.7,10-10,10C33.6,301,28.9,296.3,28.9,291.1
M20.7,290.5c0,10,8.2,18.2,18.2,18.2c10,0,18.2-8.2,18.2-18.2c0-10-8.2-18.2-18.2-18.2C28.9,272.3,20.7,280.5,20.7,290.5"/>
</defs>
<linearGradient id="SVGID_26_" gradientUnits="userSpaceOnUse" x1="20.6666" y1="290.4622" x2="56.9927" y2="290.4622">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_25_" overflow="visible" fill="url(#SVGID_26_)"/>
<clipPath id="SVGID_27_">
<use xlink:href="#SVGID_25_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_28_" gradientUnits="userSpaceOnUse" x1="-394.2999" y1="494.3898" x2="-390.3294" y2="494.3898" gradientTransform="matrix(9.1491 0 0 -9.1491 3628.1501 4813.6772)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="20.7" y="272.3" clip-path="url(#SVGID_27_)" fill="url(#SVGID_28_)" width="36.3" height="36.3"/>
</g>
<linearGradient id="SVGID_29_" gradientUnits="userSpaceOnUse" x1="258.8247" y1="159.7865" x2="500.5782" y2="159.7865">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_29_)" d="M356.9,216.1c-4.2,0.8,23.8-0.4,16.8-0.8c-1.3-0.1-2.7-0.1-4-0.1c0.2,0,0.4,0,0.6-0.1
c3.8,0,6,0,7.4,0.1c20.2,0.6,40.6,2.9,60.6-0.1c21-3.1,37.3-16.2,48.4-36.2c6.9-12.4,20.9-36.5,9.7-49.8c-5.3-6.3-13.5-9-20.6-11.3
c-11.5-3.6-23.2-6.5-35.1-8.7c-50.2-9.8-101.7-7.4-150.9,6.7c-13.3,3.9-31.3,9.3-30.9,28.2c0.3,18,13.1,37.8,24.2,49.6
C303.6,215.7,330.4,217.6,356.9,216.1L356.9,216.1z"/>
<linearGradient id="SVGID_30_" gradientUnits="userSpaceOnUse" x1="465.1524" y1="596.4427" x2="465.1524" y2="596.4427">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_30_)" d="M465.2,596.4"/>
<rect x="89.9" y="102.2" fill="none" width="583" height="649.1"/>
<linearGradient id="SVGID_31_" gradientUnits="userSpaceOnUse" x1="94.8406" y1="492.3189" x2="677.4703" y2="492.3189">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_31_)" d="M492.1,255.8c-104.5-46.7-223.2-14.1-303.7,65.2c-39.8,39.2-69.9,89.2-83.4,144.3
c-12.2,50.1-15.5,105.7,2,155.1c15.7,44.5,48.6,78.6,89.5,99.7c23.5,12.2,48.9,20.2,74.7,25.4c36.4,7.4,74,4.5,110.7,3.4l5.4,0.1
c50.3,3.4,104,0.5,151.7-15.8c27.5-9.4,53.2-24.2,75.1-43.7c44.6-40,63.9-94.3,63.4-154.1C676.4,414.9,598.7,303.5,492.1,255.8z
M530,556.3c-0.6,8-3.4,16.9-8.3,26.8c-5,9.9-11.9,18.3-20.9,25.3c-6.4,5.1-12.9,8.2-19.7,9.4c-6.8,1.2-14.6,0.6-23.5-1.8
c-18.1-4.8-33.3-10.7-45.6-17.7c-12.3-7-20.5-14-24.4-21c-6.4-11.1-6.6-31.6-0.6-61.5c0.2-1.2-0.3-2.1-1.5-2.7
c-1.2-0.6-3.6-1.1-7.1-1.6c-3.4-0.5-5.8-0.2-7.1,0.7c-1.4,1-2.3,2.9-2.7,5.8c-1.4,9.4-3.4,18.1-6,26c-2.6,8-6.1,15.3-10.6,21.9
c-4.5,6.6-10,12.5-16.5,17.7c-6.6,5.2-14.6,9.6-24.1,13.2c-8.3,3.1-16.1,4.9-23.4,5.2c-7.3,0.4-13.8-0.4-19.5-2.2
c-5.8-1.8-10.8-4.6-15-8.3c-4.3-3.7-7.4-8.3-9.4-13.6c-1.6-3.9-1.9-6.5-1-8s2.7-1.9,5.5-1.4c4.6,1,10,1.4,16.4,1.4
c6.4,0,11.9-0.2,16.7-0.7c19.9-1.9,34.9-7.3,45.1-16.1c10.2-8.8,16-19.6,17.4-32.4c0.2-3.9,0.5-7.5,0.9-10.9
c0.2-2.2-0.1-3.7-0.7-4.5c-0.7-0.8-2-1.5-4-2c-7.3-1.4-13.9-1.6-19.7-0.4c-5.8,1.2-9.8,1.1-12.1-0.2c-2.3-1.3-3.7-4.6-4.3-9.9
c-0.8-6-0.6-10.1,0.4-12.3c1.1-2.2,3.8-3.9,8.2-5.1c4.6-1.4,9.4-3.4,14.4-5.8c5.1-2.4,9.3-4.8,12.7-7.2c4.6-3.1,7.9-5.8,10-8
s3.7-4.6,4.9-7.2c0.4-1,0.2-1.9-0.4-2.9c-0.7-1-2.2-1.4-4.6-1.4c-13.9,0.7-25.1,1.8-33.5,3.1c-8.4,1.3-14.8,3-19.2,4.9
c-6.4,2.9-11.4,4-15,3.4c-3.7-0.6-6.6-3.3-8.8-8.1c-1.6-2.9-1.7-6.4-0.4-10.5c1.3-4.1,4.4-8.1,9.4-11.9c3.2-2.4,6-3.7,8.3-4
c2.4-0.2,5.9,0.5,10.4,2.2c3,1.2,7.5,1.9,13.7,2c6.2,0.1,13.6-0.2,22.3-0.9c2.4-0.5,5.7-0.7,10-0.7c4.3,0,8.4-0.1,12.4-0.4
c3-0.2,4.9-0.9,5.8-2c0.9-1.1,1.5-3.9,1.9-8.5c0.8-8.4,2.3-13.9,4.6-16.5c2.3-2.5,5.7-3.1,10.3-1.6c3.8,1.2,6.9,2.3,9.2,3.3
c2.4,1,4.3,2,5.8,3.1c1.5,1.1,2.6,2.3,3.3,3.6c0.7,1.3,1.2,2.8,1.6,4.5c0.6,2.2,0.9,4.6,0.9,7.2c0,2.4,0.6,4,1.8,4.7s4,1.2,8.3,1.4
c9.1,0.5,19.3,0.7,30.5,0.5c11.2-0.1,22.2-0.2,32.9-0.2c4,0,6.9,0.8,8.6,2.5c1.8,1.7,2.6,3.9,2.4,6.5c-0.2,2.9-0.7,5.2-1.5,7.1
c-0.8,1.8-2.3,3.2-4.5,4.2c-2.2,1-5.2,1.6-9.1,1.8c-3.9,0.2-9,0.1-15.3-0.4c-8.9-0.5-17.5-0.8-25.8-1.1c-8.2-0.2-17.1-0.4-26.7-0.4
c-1.4,0-2.9,0.5-4.6,1.4c-1.7,1-3.7,2.3-6.1,4c-7,4.6-12.5,8.2-16.5,10.9c-4.1,2.7-7.3,4.8-9.5,6.5c-2.3,1.7-3.9,3-4.9,3.8
c-1,0.8-1.9,1.6-2.7,2.4c-3.2,3.4-3.7,6.8-1.5,10.3c2.2,3.5,6.7,5.6,13.4,6.3c5,0.5,10.4-0.1,16.2-1.6c5.9-1.6,11.1-4,15.6-7.4
c2.8-1.9,2.4-4.5-1.2-7.6c-1.8-1.4-3.3-2.8-4.6-4.2c-1.3-1.3-2.4-2.6-3.4-3.8c-2-2.7-2.8-5.2-2.5-7.6c0.3-2.4,1.8-4.5,4.6-6.2
c5-3.1,11.8-2.5,20.6,1.8c8.9,4.1,16.1,8.5,21.6,13.2c5.5,4.7,10.2,10.3,14.1,16.8c2.8,4.3,4.4,7.6,4.8,9.8c0.4,2.2-0.1,4.2-1.5,6.2
c-1,1.4-2.6,2.1-4.8,1.8c-2.2-0.2-5.5-1.3-9.8-3.3c-2.4-1-4.9-2.3-7.4-4c-3.4-2.2-6.6-1.9-9.5,0.7c-2.6,2.4-4.8,4.4-6.6,6
c-1.8,1.6-4.2,3.3-7.1,5.2c-2,1.4-2.7,3.5-2.1,6.2c0.4,1.2,0.5,2.4,0.3,3.4c-0.2,1.1-0.5,2.2-0.9,3.4c-2.8,11.1-3.9,20.3-3.4,27.7
c0.5,7.4,4.2,13.4,11.2,18.3c2.8,1.9,5.5,3.6,8.2,4.9c2.7,1.3,5.7,2.4,9.1,3.1c3.4,0.7,7.3,1.2,11.9,1.4c4.6,0.2,10,0.1,16.4-0.4
c4.2-0.2,8.7-1,13.7-2.2c5-1.2,10.1-2.8,15.5-4.7c5.4-1.9,10.7-4.2,16.1-6.7c5.4-2.5,10.4-5.5,15.2-8.9c3.4-2.2,5.6-2.8,6.7-2
C530.1,547.9,530.4,551,530,556.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

169
src/assets/f_draw.svg Normal file
View File

@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 761.5 764.2" enable-background="new 0 0 761.5 764.2" xml:space="preserve">
<g>
<defs>
<path id="SVGID_1_" d="M742.3,341.5v42.2c0,2.9,2.3,5.3,5.3,5.3c2.9,0,5.3-2.3,5.3-5.3v-42.2c0-2.9-2.3-5.3-5.3-5.3
C744.6,336.2,742.3,338.6,742.3,341.5"/>
</defs>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="742.2673" y1="362.5726" x2="752.8118" y2="362.5726">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_1_" overflow="visible" fill="url(#SVGID_2_)"/>
<clipPath id="SVGID_3_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-169.8742" y1="488.795" x2="-165.9059" y2="488.795" gradientTransform="matrix(2.6571 0 0 -2.6571 1193.6409 1661.3522)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="742.3" y="336.2" clip-path="url(#SVGID_3_)" fill="url(#SVGID_4_)" width="10.5" height="52.7"/>
</g>
<g>
<defs>
<path id="SVGID_5_" d="M634.9,92.5c14.5,0,26.2,13.7,26.2,31.1c0-16.9,11.7-31.1,26.2-31.1c-14.5,0-26.2-13.7-26.2-31.1
C661.1,78.4,649.4,92.5,634.9,92.5"/>
</defs>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="634.853" y1="92.4785" x2="687.3016" y2="92.4785">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_5_" overflow="visible" fill="url(#SVGID_6_)"/>
<clipPath id="SVGID_7_">
<use xlink:href="#SVGID_5_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="-362.1954" y1="503.75" x2="-358.2255" y2="503.75" gradientTransform="matrix(13.2113 0 0 -13.2113 5419.9243 6747.6699)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="634.9" y="61.4" clip-path="url(#SVGID_7_)" fill="url(#SVGID_8_)" width="52.4" height="62.1"/>
</g>
<g>
<defs>
<path id="SVGID_9_" d="M217.4,23.8c0-7,5.9-12.9,12.9-12.9c7,0,12.9,5.9,12.9,12.9c0,7-5.9,12.9-12.9,12.9
C223.3,36.7,217.4,30.9,217.4,23.8 M230.3,1c-12.3,0-22.8,10-22.8,22.8c0,12.3,10,22.9,22.8,22.9s22.9-10,22.9-22.9
C253.1,11.5,243.2,1,230.3,1z"/>
</defs>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="207.4467" y1="23.8498" x2="253.1462" y2="23.8498">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_9_" overflow="visible" fill="url(#SVGID_10_)"/>
<clipPath id="SVGID_11_">
<use xlink:href="#SVGID_9_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="-383.9455" y1="510.4826" x2="-379.9753" y2="510.4826" gradientTransform="matrix(11.5108 0 0 -11.5108 4626.9512 5899.8926)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="207.4" y="1" clip-path="url(#SVGID_11_)" fill="url(#SVGID_12_)" width="45.7" height="45.7"/>
</g>
<g>
<defs>
<path id="SVGID_13_" d="M711.8,159.4c0-7,5.9-12.9,12.9-12.9c7,0,12.9,5.9,12.9,12.9c0,7-5.9,12.9-12.9,12.9
C717.7,172.2,711.8,166.4,711.8,159.4 M701.8,159.4c0,12.3,10,22.9,22.8,22.9s22.9-10,22.9-22.9c0-12.3-10-22.8-22.9-22.8
C712.4,136.5,701.8,146.5,701.8,159.4"/>
</defs>
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="701.8401" y1="159.3624" x2="747.5396" y2="159.3624">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_13_" overflow="visible" fill="url(#SVGID_14_)"/>
<clipPath id="SVGID_15_">
<use xlink:href="#SVGID_13_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="-352.1799" y1="501.9952" x2="-348.2098" y2="501.9952" gradientTransform="matrix(11.5108 0 0 -11.5108 4755.6987 5937.7085)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="701.8" y="136.5" clip-path="url(#SVGID_15_)" fill="url(#SVGID_16_)" width="45.7" height="45.7"/>
</g>
<g>
<defs>
<path id="SVGID_17_" d="M143.3,173.8c0,7.8,6.3,14.4,14.4,14.4c8.1,0,14.4-6.3,14.4-14.4c0-7.8-6.3-14.4-14.4-14.4
C150,159.4,143.3,165.7,143.3,173.8"/>
</defs>
<linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="143.3141" y1="173.7856" x2="172.1528" y2="173.7856">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_17_" overflow="visible" fill="url(#SVGID_18_)"/>
<clipPath id="SVGID_19_">
<use xlink:href="#SVGID_17_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_20_" gradientUnits="userSpaceOnUse" x1="-379.4139" y1="507.9658" x2="-375.4444" y2="507.9658" gradientTransform="matrix(7.265 0 0 -7.265 2899.7617 3864.1646)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="143.3" y="159.4" clip-path="url(#SVGID_19_)" fill="url(#SVGID_20_)" width="28.8" height="28.8"/>
</g>
<g>
<defs>
<path id="SVGID_21_" d="M3.1,91.8c35.1,0,63.3,33.4,63.3,75c0-41.6,28.1-75,63.3-75c-35.2,0-63.3-33.4-63.3-75
C66.3,57.8,38.2,91.8,3.1,91.8"/>
</defs>
<linearGradient id="SVGID_22_" gradientUnits="userSpaceOnUse" x1="3.0593" y1="91.7778" x2="129.6054" y2="91.7778">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_21_" overflow="visible" fill="url(#SVGID_22_)"/>
<clipPath id="SVGID_23_">
<use xlink:href="#SVGID_21_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_24_" gradientUnits="userSpaceOnUse" x1="-400.916" y1="494.761" x2="-396.946" y2="494.761" gradientTransform="matrix(31.8752 0 0 -31.8752 12782.3242 15862.3652)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="3.1" y="16.8" clip-path="url(#SVGID_23_)" fill="url(#SVGID_24_)" width="126.5" height="150"/>
</g>
<g>
<defs>
<path id="SVGID_25_" d="M28.9,291.1c0-5.3,4.7-10,10-10c5.9,0,10,4.1,10,10c0,5.3-4.7,10-10,10C33.6,301,28.9,296.3,28.9,291.1
M20.7,290.5c0,10,8.2,18.2,18.2,18.2c10,0,18.2-8.2,18.2-18.2c0-10-8.2-18.2-18.2-18.2C28.9,272.3,20.7,280.5,20.7,290.5"/>
</defs>
<linearGradient id="SVGID_26_" gradientUnits="userSpaceOnUse" x1="20.6666" y1="290.4622" x2="56.9927" y2="290.4622">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_25_" overflow="visible" fill="url(#SVGID_26_)"/>
<clipPath id="SVGID_27_">
<use xlink:href="#SVGID_25_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_28_" gradientUnits="userSpaceOnUse" x1="-394.2999" y1="494.3898" x2="-390.3294" y2="494.3898" gradientTransform="matrix(9.1491 0 0 -9.1491 3628.1501 4813.6772)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="20.7" y="272.3" clip-path="url(#SVGID_27_)" fill="url(#SVGID_28_)" width="36.3" height="36.3"/>
</g>
<linearGradient id="SVGID_29_" gradientUnits="userSpaceOnUse" x1="465.1524" y1="596.4427" x2="465.1524" y2="596.4427">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_29_)" d="M465.2,596.4"/>
<rect x="89.9" y="102.2" fill="none" width="583" height="649.1"/>
<linearGradient id="SVGID_30_" gradientUnits="userSpaceOnUse" x1="98.9953" y1="524.7551" x2="672.8892" y2="524.7551">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_30_)" d="M664.8,583.8h-89.5c-31.7,0-57.5-25.7-57.5-57.5c0-31.7,25.7-57.5,57.5-57.5h89.4
c2.7,0,5.5,0.2,8.1,0.5V347.3c-2.7-26.5-24.2-47.2-50.9-48.3H152.3c-29.4,0-53.2,24-53.3,53.7v351.6c3.1,23.9,21.7,42.9,45.3,46.3
H631c21.1-4.2,37.8-21.1,41.9-42.5V583.3C670.2,583.7,667.5,583.8,664.8,583.8L664.8,583.8z M664.8,583.8"/>
<linearGradient id="SVGID_31_" gradientUnits="userSpaceOnUse" x1="348.0981" y1="368.2679" x2="615.9556" y2="368.2679">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_31_)" d="M565,525.6c0,14.1,11.4,25.5,25.5,25.5c14.1,0,25.5-11.4,25.5-25.5c0-14.1-11.4-25.5-25.5-25.5
C576.4,500.1,565,511.5,565,525.6L565,525.6z M581.8,265.4l-31.3-56.6c-15.1-21.9-44.1-29.6-67.9-17.7l-134.5,74.3H581.8z
M581.8,265.4"/>
<rect x="99" y="185.3" fill="none" width="574.3" height="565.9"/>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

156
src/assets/f_trade.svg Normal file
View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 761.5 764.2" enable-background="new 0 0 761.5 764.2" xml:space="preserve">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="89.8781" y1="458.6625" x2="672.8819" y2="458.6625">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M345.9,480l75.1,75.1l72.5-72.5c4.3-4.3,10.2-6.9,16.7-6.9h162.2c0.3-5.6,0.5-11.3,0.5-17
c0-161.2-130.7-291.9-291.9-291.9c-154.2,0-280.4,119.5-291.1,271h138.6l82.3-82.3c9.2-9.2,24.1-9.2,33.3,0l91.8,91.8
c8.6,9.3,8.4,23.7-0.6,32.7c-9,9-23.4,9.2-32.7,0.6l-75.1-75.1L254.9,478c-4.4,4.4-10.4,6.9-16.7,6.9H90.3
C103.6,633.8,228.6,750.6,381,750.6c139.2,0,255.6-97.4,284.8-227.7H520l-82.3,82.3c-9.2,9.2-24.1,9.2-33.3,0l-91.8-91.8
c-6-5.9-8.3-14.6-6.1-22.8c2.2-8.1,8.5-14.5,16.6-16.7C331.2,471.8,339.9,474.1,345.9,480L345.9,480z M345.9,480"/>
<g>
<defs>
<path id="SVGID_2_" d="M742.3,341.5v42.2c0,2.9,2.3,5.3,5.3,5.3c2.9,0,5.3-2.3,5.3-5.3v-42.2c0-2.9-2.3-5.3-5.3-5.3
C744.6,336.2,742.3,338.6,742.3,341.5"/>
</defs>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="742.2673" y1="362.5726" x2="752.8118" y2="362.5726">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_2_" overflow="visible" fill="url(#SVGID_3_)"/>
<clipPath id="SVGID_4_">
<use xlink:href="#SVGID_2_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="-169.8742" y1="488.795" x2="-165.9059" y2="488.795" gradientTransform="matrix(2.6571 0 0 -2.6571 1193.6409 1661.3522)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="742.3" y="336.2" clip-path="url(#SVGID_4_)" fill="url(#SVGID_5_)" width="10.5" height="52.7"/>
</g>
<g>
<defs>
<path id="SVGID_6_" d="M634.9,92.5c14.5,0,26.2,13.7,26.2,31.1c0-16.9,11.7-31.1,26.2-31.1c-14.5,0-26.2-13.7-26.2-31.1
C661.1,78.4,649.4,92.5,634.9,92.5"/>
</defs>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="634.853" y1="92.4785" x2="687.3016" y2="92.4785">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_6_" overflow="visible" fill="url(#SVGID_7_)"/>
<clipPath id="SVGID_8_">
<use xlink:href="#SVGID_6_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="-362.1954" y1="503.75" x2="-358.2255" y2="503.75" gradientTransform="matrix(13.2113 0 0 -13.2113 5419.9243 6747.6699)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="634.9" y="61.4" clip-path="url(#SVGID_8_)" fill="url(#SVGID_9_)" width="52.4" height="62.1"/>
</g>
<g>
<defs>
<path id="SVGID_10_" d="M217.4,23.8c0-7,5.9-12.9,12.9-12.9c7,0,12.9,5.9,12.9,12.9c0,7-5.9,12.9-12.9,12.9
C223.3,36.7,217.4,30.9,217.4,23.8 M230.3,1c-12.3,0-22.8,10-22.8,22.8c0,12.3,10,22.9,22.8,22.9s22.9-10,22.9-22.9
C253.1,11.5,243.2,1,230.3,1z"/>
</defs>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="207.4467" y1="23.8498" x2="253.1462" y2="23.8498">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_10_" overflow="visible" fill="url(#SVGID_11_)"/>
<clipPath id="SVGID_12_">
<use xlink:href="#SVGID_10_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="-383.9455" y1="510.4826" x2="-379.9753" y2="510.4826" gradientTransform="matrix(11.5108 0 0 -11.5108 4626.9512 5899.8926)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="207.4" y="1" clip-path="url(#SVGID_12_)" fill="url(#SVGID_13_)" width="45.7" height="45.7"/>
</g>
<g>
<defs>
<path id="SVGID_14_" d="M728.5,174c0-7,5.9-12.9,12.9-12.9s12.9,5.9,12.9,12.9c0,7-5.9,12.9-12.9,12.9S728.5,181,728.5,174
M718.6,174c0,12.3,10,22.9,22.8,22.9s22.9-10,22.9-22.9c0-12.3-10-22.8-22.9-22.8C729.1,151.1,718.6,161.1,718.6,174"/>
</defs>
<linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="718.5818" y1="173.9822" x2="764.2813" y2="173.9822">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_14_" overflow="visible" fill="url(#SVGID_15_)"/>
<clipPath id="SVGID_16_">
<use xlink:href="#SVGID_14_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="-350.7255" y1="500.7251" x2="-346.7553" y2="500.7251" gradientTransform="matrix(11.5108 0 0 -11.5108 4755.6987 5937.7085)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="718.6" y="151.1" clip-path="url(#SVGID_16_)" fill="url(#SVGID_17_)" width="45.7" height="45.7"/>
</g>
<g>
<defs>
<path id="SVGID_18_" d="M143.3,173.8c0,7.8,6.3,14.4,14.4,14.4c8.1,0,14.4-6.3,14.4-14.4c0-7.8-6.3-14.4-14.4-14.4
C150,159.4,143.3,165.7,143.3,173.8"/>
</defs>
<linearGradient id="SVGID_19_" gradientUnits="userSpaceOnUse" x1="143.3141" y1="173.7856" x2="172.1528" y2="173.7856">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_18_" overflow="visible" fill="url(#SVGID_19_)"/>
<clipPath id="SVGID_20_">
<use xlink:href="#SVGID_18_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_21_" gradientUnits="userSpaceOnUse" x1="-379.4139" y1="507.9658" x2="-375.4444" y2="507.9658" gradientTransform="matrix(7.265 0 0 -7.265 2899.7617 3864.1646)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="143.3" y="159.4" clip-path="url(#SVGID_20_)" fill="url(#SVGID_21_)" width="28.8" height="28.8"/>
</g>
<g>
<defs>
<path id="SVGID_22_" d="M3.1,91.8c35.1,0,63.3,33.4,63.3,75c0-41.6,28.1-75,63.3-75c-35.2,0-63.3-33.4-63.3-75
C66.3,57.8,38.2,91.8,3.1,91.8"/>
</defs>
<linearGradient id="SVGID_23_" gradientUnits="userSpaceOnUse" x1="3.0593" y1="91.7778" x2="129.6054" y2="91.7778">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_22_" overflow="visible" fill="url(#SVGID_23_)"/>
<clipPath id="SVGID_24_">
<use xlink:href="#SVGID_22_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_25_" gradientUnits="userSpaceOnUse" x1="-400.916" y1="494.761" x2="-396.946" y2="494.761" gradientTransform="matrix(31.8752 0 0 -31.8752 12782.3242 15862.3652)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="3.1" y="16.8" clip-path="url(#SVGID_24_)" fill="url(#SVGID_25_)" width="126.5" height="150"/>
</g>
<g>
<defs>
<path id="SVGID_26_" d="M28.9,291.1c0-5.3,4.7-10,10-10c5.9,0,10,4.1,10,10c0,5.3-4.7,10-10,10C33.6,301,28.9,296.3,28.9,291.1
M20.7,290.5c0,10,8.2,18.2,18.2,18.2c10,0,18.2-8.2,18.2-18.2c0-10-8.2-18.2-18.2-18.2C28.9,272.3,20.7,280.5,20.7,290.5"/>
</defs>
<linearGradient id="SVGID_27_" gradientUnits="userSpaceOnUse" x1="20.6666" y1="290.4622" x2="56.9927" y2="290.4622">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<use xlink:href="#SVGID_26_" overflow="visible" fill="url(#SVGID_27_)"/>
<clipPath id="SVGID_28_">
<use xlink:href="#SVGID_26_" overflow="visible"/>
</clipPath>
<linearGradient id="SVGID_29_" gradientUnits="userSpaceOnUse" x1="-394.2999" y1="494.3898" x2="-390.3294" y2="494.3898" gradientTransform="matrix(9.1491 0 0 -9.1491 3628.1501 4813.6772)">
<stop offset="0" style="stop-color:#FC4E2B"/>
<stop offset="1" style="stop-color:#FC962B"/>
</linearGradient>
<rect x="20.7" y="272.3" clip-path="url(#SVGID_28_)" fill="url(#SVGID_29_)" width="36.3" height="36.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
src/assets/failure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

14
src/assets/fund.svg Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 758 758" enable-background="new 0 0 758 758" xml:space="preserve">
<path fill="#CDCDCD" d="M731.8,671.8V191.5c0-47.4-47.8-47.4-47.8-47.4h-15.6v-62c0-44.6-46-46-46-46h-503
C28.9,36,24,130.2,24,130.2v541c0,47.4,47.4,46.7,47.4,46.7h614C734.9,717.8,731.8,671.8,731.8,671.8z M667.7,671.1H87.7
c0,0-17,5.6-17-15.3V132.3c0,0,1.4-49.5,46-49.5h488.8c0,0,0.8-0.3,2.2-0.5h5.3c4.8,0.8,10.2,4.3,10.2,15.8v46H150.6v47.4h519.9
c0,0,14.9-4.9,14.9,15.3v448.3C685.4,655.1,688.9,671.1,667.7,671.1z"/>
<path fill="#CDCDCD" d="M503.5,348.4c-47.8,0-86.5,38.2-86.5,85.2c0,47.1,38.7,85.2,86.5,85.2c47.8,0,86.5-38.2,86.5-85.2
C590,386.6,551.3,348.4,503.5,348.4z M503,480.2c-25.2,0-45.6-20.1-45.6-45c0-24.8,20.4-45,45.6-45c25.2,0,45.6,20.1,45.6,45
C548.6,460,528.2,480.2,503,480.2z"/>
<rect x="24" y="36" fill="none" width="708" height="682.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

15
src/assets/fund_s.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 758 758" enable-background="new 0 0 758 758" xml:space="preserve">
<polygon fill="#E5E5E5" points="56,250 634,250 634,670.7 56,680 56,260.5 "/>
<path d="M731.8,671.8V191.5c0-47.4-47.8-47.4-47.8-47.4h-15.6v-62c0-44.6-46-46-46-46h-503C28.9,36,24,130.2,24,130.2v541
c0,47.4,47.4,46.7,47.4,46.7h614C734.9,717.8,731.8,671.8,731.8,671.8z M667.7,671.1H87.7c0,0-17,5.6-17-15.3V132.3
c0,0,1.4-49.5,46-49.5h488.8c0,0,0.8-0.3,2.2-0.5h5.3c4.8,0.8,10.2,4.3,10.2,15.8v46H150.6v47.4h519.9c0,0,14.9-4.9,14.9,15.3v448.3
C685.4,655.1,688.9,671.1,667.7,671.1z"/>
<path d="M503.5,348.4c-47.8,0-86.5,38.2-86.5,85.2c0,47.1,38.7,85.2,86.5,85.2c47.8,0,86.5-38.2,86.5-85.2
C590,386.6,551.3,348.4,503.5,348.4z M503,480.2c-25.2,0-45.6-20.1-45.6-45c0-24.8,20.4-45,45.6-45c25.2,0,45.6,20.1,45.6,45
C548.6,460,528.2,480.2,503,480.2z"/>
<rect x="24" y="36" fill="none" width="708" height="682.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

17
src/assets/home.svg Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 830 841.9" enable-background="new 0 0 830 841.9" xml:space="preserve">
<path fill="#CDCDCD" d="M146,842.9c-0.6-0.3-1.1-0.8-1.7-1c-15.2-4.3-23-14.4-23-30.3c0-72.8,0-145.7,0-218.5c0-26,0-52,0-78
c0-13.4,6.8-23.9,18.2-28.4c19-7.5,39,6,39.2,26.7c0.1,16.7,0,33.3,0,50c0,71.7,0,143.3,0,215c0,6.9,0,6.9,7.1,6.9
c131.3,0,262.6,0,394,0c22.7,0,45.3,0.1,68,0.5c5,0.1,6.5-1.4,6.5-6.5c-0.1-88.5,0-177-0.3-265.5c0-13.6,9.9-27.1,24-28.6
c16.3-1.8,28.9,7,32.3,22.3c0.5,2.1,0.4,4.3,0.4,6.4c0,99.7,0,199.3,0,299c0,15-7.9,25.1-22.5,28.9c-0.8,0.2-1.5,0.7-2.2,1
C506,842.9,326,842.9,146,842.9z"/>
<path fill="#CDCDCD" d="M420,2.9c16.3,4.7,25.9,18.2,37.4,28.9C570.2,137,682.7,242.4,795.3,347.8c6.1,5.7,12,11.5,18.3,17
c7.4,6.4,13.8,13.4,16.4,23.1c0,2.7,0,5.3,0,8c-0.4,0.9-0.9,1.8-1.1,2.7c-6.2,22.3-31.1,29.5-48,13.8c-10.4-9.6-20.7-19.3-31-29
c-74.3-69.5-148.6-139.1-222.9-208.6c-35.8-33.5-71.5-66.9-107.3-100.3c-1.2-1.1-2.5-2.1-4.1-3.5c-9,8.3-17.7,16.4-26.5,24.6
c-77,71.8-154,143.6-231,215.3c-35.8,33.4-72,66.4-107.3,100.3c-18,17.3-45.9,8.9-49.9-13.5c-0.1-0.6-0.6-1.2-0.9-1.8
c0-2.7,0-5.3,0-8c3.9-15.4,17-23.4,27.5-33.2c108.4-101.3,217-202.4,325.6-303.5c13.9-12.9,27.5-26.2,41.8-38.7
c4.8-4.2,11.4-6.4,17.1-9.5C414.7,2.9,417.3,2.9,420,2.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

18
src/assets/home_s.svg Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 830 841.9" enable-background="new 0 0 830 841.9" xml:space="preserve">
<polygon fill="#E5E5E5" points="149,381.9 403,151.9 591,381.9 591,781.9 179,781.9 149,781.9 "/>
<path d="M146,842.9c-0.6-0.3-1.1-0.8-1.7-1c-15.2-4.3-23-14.4-23-30.3c0-72.8,0-145.7,0-218.5c0-26,0-52,0-78
c0-13.4,6.8-23.9,18.2-28.4c19-7.5,39,6,39.2,26.7c0.1,16.7,0,33.3,0,50c0,71.7,0,143.3,0,215c0,6.9,0,6.9,7.1,6.9
c131.3,0,262.6,0,394,0c22.7,0,45.3,0.1,68,0.5c5,0.1,6.5-1.4,6.5-6.5c-0.1-88.5,0-177-0.3-265.5c0-13.6,9.9-27.1,24-28.6
c16.3-1.8,28.9,7,32.3,22.3c0.5,2.1,0.4,4.3,0.4,6.4c0,99.7,0,199.3,0,299c0,15-7.9,25.1-22.5,28.9c-0.8,0.2-1.5,0.7-2.2,1
C506,842.9,326,842.9,146,842.9z"/>
<path fill="#020101" d="M420,2.9c16.3,4.7,25.9,18.2,37.4,28.9C570.2,137,682.7,242.4,795.3,347.8c6.1,5.7,12,11.5,18.3,17
c7.4,6.4,13.8,13.4,16.4,23.1c0,2.7,0,5.3,0,8c-0.4,0.9-0.9,1.8-1.1,2.7c-6.2,22.3-31.1,29.5-48,13.8c-10.4-9.6-20.7-19.3-31-29
c-74.3-69.5-148.6-139.1-222.9-208.6c-35.8-33.5-71.5-66.9-107.3-100.3c-1.2-1.1-2.5-2.1-4.1-3.5c-9,8.3-17.7,16.4-26.5,24.6
c-77,71.8-154,143.6-231,215.3c-35.8,33.4-72,66.4-107.3,100.3c-18,17.3-45.9,8.9-49.9-13.5c-0.1-0.6-0.6-1.2-0.9-1.8
c0-2.7,0-5.3,0-8c3.9-15.4,17-23.4,27.5-33.2c108.4-101.3,217-202.4,325.6-303.5c13.9-12.9,27.5-26.2,41.8-38.7
c4.8-4.2,11.4-6.4,17.1-9.5C414.7,2.9,417.3,2.9,420,2.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/m_banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

18
src/assets/market.svg Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 735 736.5" enable-background="new 0 0 735 736.5" xml:space="preserve">
<path fill="#CDCDCD" d="M485.7,312.4c-5.3-7.4-14-6.2-14-6.2c-9,0-16.5,6.5-18.1,15.3c-7.2,40.5-42.6,71.2-85.2,71.2
c-42.6,0-78-30.7-85.2-71.2c-1.6-8.8-9.1-15.3-18.1-15.3h-0.5c-11.4,0-20,10.3-18.1,21.4c10.2,58.1,60.8,102.2,121.8,102.2
c60.6,0,111.1-43.7,121.6-101.3C491.4,321.4,488.5,316.7,485.7,312.4L485.7,312.4z M485.7,312.4"/>
<path fill="#CDCDCD" d="M727,435c0-62.7-35.1-117.3-86.8-145.5c-2.4-148.2-123-267.6-271.8-267.6C219.7,21.9,99,141.3,96.7,289.5
C45,317.7,9.9,372.3,9.9,435c0,76.6,52.3,140.9,123.4,159.9l-30,96.2c-3.6,11.5,5,23.1,17,23.1h0.4c7.7,0,14.5-4.9,16.9-12.2
l8.3-24.9l33.5-111.5c-0.9,0-1.7,0.3-2.5,0.3C105.1,565.9,47,507.8,47,436.1c0-42.6,20.8-80.1,52.5-103.7
c14.1,99.1,81.4,180.8,172.1,215.4l-40,129.4h0l-4.3,14c-3.6,11.5,5,23.1,17,23.1h0.1c7.7,0,14.5-4.9,16.9-12.2l8.3-24.9l53.2-169.3
C215,486.6,149.9,398.5,149.9,294.4c0-117,87.6-213,216.3-214.1C497,79.1,587,175,587,293.9c0,102.1-65,192.7-172.8,214l53.2,169.3
l8.3,24.9c2.4,7.3,9.2,12.2,16.9,12.2c12,0,20.6-11.7,17-23.1l-4.4-13.9h0.1l-40-129.4c90.7-34.6,158.1-116.4,172.1-215.5
c31.7,23.7,52.5,61.2,52.5,103.8c0,71.7-58.1,129.8-129.7,129.8c-0.9,0-1.7-0.2-2.6-0.3l33.4,111.5H591l8.3,24.9
c2.4,7.3,9.2,12.2,16.9,12.2h0.4c12,0,20.6-11.6,17-23.1l-29.9-96.2C674.7,575.9,727,511.6,727,435L727,435z"/>
<path d="M727,435"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

22
src/assets/market_s.svg Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 735 736.5" enable-background="new 0 0 735 736.5" xml:space="preserve">
<polygon fill="#E5E5E5" points="190.8,520.3 337.1,702.7 112.9,705.1 "/>
<ellipse fill="#E5E5E5" cx="134.6" cy="471.2" rx="113.8" ry="118.2"/>
<ellipse fill="#E5E5E5" cx="332.8" cy="328.7" rx="207.2" ry="200.8"/>
<path d="M485.7,312.4c-5.3-7.4-14-6.2-14-6.2c-9,0-16.5,6.5-18.1,15.3c-7.2,40.5-42.6,71.2-85.2,71.2c-42.6,0-78-30.7-85.2-71.2
c-1.6-8.8-9.1-15.3-18.1-15.3h-0.5c-11.4,0-20,10.3-18.1,21.4c10.2,58.1,60.8,102.2,121.8,102.2c60.6,0,111.1-43.7,121.6-101.3
C491.4,321.4,488.5,316.7,485.7,312.4L485.7,312.4z M485.7,312.4"/>
<path d="M727,435c0-62.7-35.1-117.3-86.8-145.5c-2.4-148.2-123-267.6-271.8-267.6C219.7,21.9,99,141.3,96.7,289.5
C45,317.7,9.9,372.3,9.9,435c0,76.6,52.3,140.9,123.4,159.9l-30,96.2c-3.6,11.5,5,23.1,17,23.1h0.4c7.7,0,14.5-4.9,16.9-12.2
l8.3-24.9l33.5-111.5c-0.9,0-1.7,0.3-2.5,0.3C105.1,565.9,47,507.8,47,436.1c0-42.6,20.8-80.1,52.5-103.7
c14.1,99.1,81.4,180.8,172.1,215.4l-40,129.4h0l-4.3,14c-3.6,11.5,5,23.1,17,23.1h0.1c7.7,0,14.5-4.9,16.9-12.2l8.3-24.9l53.2-169.3
C215,486.6,149.9,398.5,149.9,294.4c0-117,87.6-213,216.3-214.1C497,79.1,587,175,587,293.9c0,102.1-65,192.7-172.8,214l53.2,169.3
l8.3,24.9c2.4,7.3,9.2,12.2,16.9,12.2c12,0,20.6-11.7,17-23.1l-4.4-13.9h0.1l-40-129.4c90.7-34.6,158.1-116.4,172.1-215.5
c31.7,23.7,52.5,61.2,52.5,103.8c0,71.7-58.1,129.8-129.7,129.8c-0.9,0-1.7-0.2-2.6-0.3l33.4,111.5H591l8.3,24.9
c2.4,7.3,9.2,12.2,16.9,12.2h0.4c12,0,20.6-11.6,17-23.1l-29.9-96.2C674.7,575.9,727,511.6,727,435L727,435z"/>
<path d="M727,435"/>
<polygon fill="#E5E5E5" points="318,493.5 248.3,706.5 433.5,706.5 378,493.5 "/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
src/assets/mine_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
src/assets/p_about.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/p_account.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/assets/p_identity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/assets/p_msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/assets/p_share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/p_system.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

19
src/assets/profile.svg Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 825 841.9" enable-background="new 0 0 825 841.9" xml:space="preserve">
<path fill="#CDCDCD" d="M789,842.9c-0.4-0.3-0.8-0.7-1.2-0.8c-18.6-5.5-25.9-15.2-25.9-34.4c0-24-0.9-48,0.2-72
c1.3-28.2-9-51.7-26.1-72.9c-25.1-30.9-56.7-53.7-91.2-72.7c-50-27.5-103.4-45-159.9-53.6c-26-3.9-52.2-6.1-78.5-5.5
c-51.8,1.3-101.9,11.9-150.7,28.6c-43.7,14.9-85.2,34.1-123,61c-20.6,14.7-39.4,31.3-53.6,52.5c-10.8,16.1-16.4,33.5-16.1,53
c0.4,27.1,0.1,54.3,0.1,81.5c0,19.2-7.3,28.9-25.9,34.4c-0.4,0.1-0.8,0.6-1.2,0.8c-2.7,0-5.3,0-8,0c-1.1-0.4-2.1-0.9-3.2-1.2
c-11.6-2.8-19.1-10-23-21.2c-0.6-1.9-1.2-3.7-1.8-5.6c0-39.7,0-79.3,0-119c0.3-0.9,0.9-1.8,1-2.8C2.9,678,8.6,664.2,15.7,651
c20.8-38.5,50.7-68.7,85.9-94c47.5-34.3,100.7-55.8,157.3-69.8c1.3-0.3,2.6-0.9,4.6-1.5c-2-1.5-3.2-2.6-4.6-3.6
c-19.4-14-36.9-30.2-51.7-49c-50.8-64.6-68.8-137.5-53.4-218.1c10.4-54.3,36.5-100.7,76.7-138.9C273.2,35.5,324,11.7,382.8,5
c6.1-0.7,12.2-1.4,18.2-2.1c7.7,0,15.3,0,23,0c2.1,0.4,4.2,0.8,6.3,1.1c14,2,28.1,3.1,41.9,6.2C604.3,40.5,695,167.9,673.4,307.9
c-11,70.9-46.5,127.8-103.5,171.2c-1.5,1.1-3,2.3-5.1,4.1c1.8,0.6,2.5,0.9,3.2,1.1c24.9,5.9,49.4,13.3,73.2,23
c52.2,21.4,98.6,51.2,135.6,94.4c20.6,24,37.2,50.3,45.3,81.3c1,4,1.9,8,2.9,12c0,40,0,80,0,120c-0.4,1.2-0.9,2.5-1.3,3.7
c-3,11.3-10.1,18.7-21.1,22.5c-1.9,0.6-3.7,1.2-5.6,1.8C794.3,842.9,791.7,842.9,789,842.9z M412.3,468.2
c109.6-0.1,201.5-87.9,201.4-201.4C613.6,153.8,522.5,66,413,65.7c-110-0.3-201.6,87.8-201.7,201.1
C211.3,380.2,303,467.9,412.3,468.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

22
src/assets/profile_s.svg Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 825 841.9" enable-background="new 0 0 825 841.9" xml:space="preserve">
<rect x="66" y="594.9" fill="#E5E5E5" width="559" height="247"/>
<path fill="#E5E5E5" d="M387.2,465.2h-33.4c-93.5,0-170.1-76.5-170.1-170.1v-33.4c0-93.5,76.5-170.1,170.1-170.1h33.4
c93.5,0,170.1,76.5,170.1,170.1v33.4C557.3,388.6,480.8,465.2,387.2,465.2z"/>
<path d="M789,842.9c-0.4-0.3-0.8-0.7-1.2-0.8c-18.6-5.5-25.9-15.2-25.9-34.4c0-24-0.9-48,0.2-72c1.3-28.2-9-51.7-26.1-72.9
c-25.1-30.9-56.7-53.7-91.2-72.7c-50-27.5-103.4-45-159.9-53.6c-26-3.9-52.2-6.1-78.5-5.5c-51.8,1.3-101.9,11.9-150.7,28.6
c-43.7,14.9-85.2,34.1-123,61c-20.6,14.7-39.4,31.3-53.6,52.5c-10.8,16.1-16.4,33.5-16.1,53c0.4,27.1,0.1,54.3,0.1,81.5
c0,19.2-7.3,28.9-25.9,34.4c-0.4,0.1-0.8,0.6-1.2,0.8c-2.7,0-5.3,0-8,0c-1.1-0.4-2.1-0.9-3.2-1.2c-11.6-2.8-19.1-10-23-21.2
c-0.6-1.9-1.2-3.7-1.8-5.6c0-39.7,0-79.3,0-119c0.3-0.9,0.9-1.8,1-2.8C2.9,678,8.6,664.2,15.7,651c20.8-38.5,50.7-68.7,85.9-94
c47.5-34.3,100.7-55.8,157.3-69.8c1.3-0.3,2.6-0.9,4.6-1.5c-2-1.5-3.2-2.6-4.6-3.6c-19.4-14-36.9-30.2-51.7-49
c-50.8-64.6-68.8-137.5-53.4-218.1c10.4-54.3,36.5-100.7,76.7-138.9C273.2,35.5,324,11.7,382.8,5c6.1-0.7,12.2-1.4,18.2-2.1
c7.7,0,15.3,0,23,0c2.1,0.4,4.2,0.8,6.3,1.1c14,2,28.1,3.1,41.9,6.2C604.3,40.5,695,167.9,673.4,307.9
c-11,70.9-46.5,127.8-103.5,171.2c-1.5,1.1-3,2.3-5.1,4.1c1.8,0.6,2.5,0.9,3.2,1.1c24.9,5.9,49.4,13.3,73.2,23
c52.2,21.4,98.6,51.2,135.6,94.4c20.6,24,37.2,50.3,45.3,81.3c1,4,1.9,8,2.9,12c0,40,0,80,0,120c-0.4,1.2-0.9,2.5-1.3,3.7
c-3,11.3-10.1,18.7-21.1,22.5c-1.9,0.6-3.7,1.2-5.6,1.8C794.3,842.9,791.7,842.9,789,842.9z M412.3,468.2
c109.6-0.1,201.5-87.9,201.4-201.4C613.6,153.8,522.5,66,413,65.7c-110-0.3-201.6,87.8-201.7,201.1
C211.3,380.2,303,467.9,412.3,468.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/assets/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { formatMessage } from 'umi-plugin-locale';
import { Icon } from 'antd';
import styles from './style.less';
export interface AccountItemProps {
title: string;
message?: string;
preview?: string;
onClick?: () => any;
className?: string;
showGo?: boolean;
}
const AccountItem: React.FC<AccountItemProps> = ({
title,
message = '',
preview = '',
onClick,
className = '',
showGo = true,
}) => {
const titleText = formatMessage({ id: title });
return (
<div className={`${styles.item} ${className}`} onClick={onClick}>
<span className={styles.title}>{titleText}</span>
<span className={styles.message}>{message}</span>
<span className={styles.preview}>{preview}</span>
{showGo && <Icon className={styles.go} type="right" />}
</div>
);
};
export default AccountItem;

View File

@@ -0,0 +1,43 @@
@import '../../global.less';
.item {
background-color: #fff;
padding: 10px 20px;
display: flex;
flex-direction: row;
align-items: center;
transition: background-color .2s;
user-select: none;
&:active {
background-color: @bg-color;
}
}
.title {
@titleWidth: 80px;
// flex-basis: @titleWidth;
min-width: @titleWidth;
}
.message {
flex-grow: 1;
flex-basis: 0;
margin: 0 10px;
color: @primary-color;
}
.preview {
flex-grow: 1;
flex-basis: 0;
margin: 0 10px;
color: @hint-color;
text-align: right;
}
.go {
flex-shrink: 0;
color: lightgrey;
font-size: 1.2rem;
}

View File

@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import { Select } from 'antd';
import _ from 'lodash';
import { AreaCodes, AreaCode } from '@/types/common';
import styles from './style.less';
const Option = Select.Option;
interface AreaSelectionProps {
areacodes: AreaCodes;
areacode?: AreaCode;
onChange?: (areacode: AreaCode) => void;
className?: string;
size?: 'large' | 'default' | 'small',
}
const AreaSelection: React.FC<AreaSelectionProps> = ({
areacode,
areacodes,
onChange,
className = '',
size,
}) => {
const [value, setValue] = useState(areacodes[0]);
const onValueChange = (id: string) => {
const target = _.find(areacodes, { id });
if (target) {
if (onChange) { onChange(target); }
if (!areacode) { setValue(target); }
}
};
const actual = areacode || value;
return (
<div className={`${styles.component} ${className}`}>
<div className="name">
<Select
value={actual && actual.id}
onChange={onValueChange}
size={size}
>
{areacodes.map(it => (
<Option
key={it.id}
value={it.id}
>{it.areaname}</Option>
))}
</Select>
</div>
<div className="code">
<Select
value={actual && actual.id}
onChange={onValueChange}
size={size}
>
{areacodes.map(it => (
<Option
key={it.id}
value={it.id}
>+{it.areacode}</Option>
))}
</Select>
</div>
</div>
)
}
export default AreaSelection;

View File

@@ -0,0 +1,15 @@
.component {
display: flex;
justify-content: space-between;
:global {
.ant-select {
width: 100%;
}
.name, .code {
width: 48%;
}
}
}

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { Modal } from 'antd';
import { ModalProps } from 'antd/lib/modal';
import styles from './style.less';
export interface ChoiceItem {
key: string;
value: string;
}
export interface ChoiceModal {
data: ChoiceItem[],
selectedKey: string;
onItemSelected?: (key: string) => any;
}
const ChoiceModal: React.FC<ChoiceModal & ModalProps> = ({
data,
selectedKey,
onItemSelected,
...props
}) => {
const renderItem = (it: ChoiceItem) => (
<div
key={it.key}
className={`${styles.item} ${it.key === selectedKey ? styles.active : ''}`}
onClick={() => { onItemSelected && onItemSelected(it.key); }}
>
{it.value}
</div>
);
return (
<Modal
{...props}
footer={null}
>
<div className={styles.list}>
{data.map(renderItem)}
</div>
</Modal>
)
};
export default ChoiceModal;

View File

@@ -0,0 +1,19 @@
@import '../../global.less';
@item-height: 48px;
.list {
max-height: @item-height * 6;
overflow: auto;
}
.item {
height: @item-height;
text-align: center;
line-height: @item-height;
}
.active {
background-color: lighten(@primary-color, 20%);
color: white;
}

View File

@@ -0,0 +1,3 @@
import { Empty } from 'antd';
export default Empty;

View File

@@ -0,0 +1,12 @@
import React from 'react';
import failureIcon from '@/assets/failure.png';
// import styles from './style.less';
const Failure: React.FC = () => {
return (
<img style={{ width: 160 }} src={failureIcon} />
);
};
export default Failure;

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { Icon } from 'antd';
import { dispatch } from '@/utils/utils';
import styles from './style.less';
import { routerRedux } from 'dva';
export interface HeaderProps {
className?: string;
title?: string | React.ReactNode;
showReturn?: boolean;
rightContent?: string | React.ReactNode;
onReturn?: () => any;
}
const Header: React.FC<HeaderProps> = ({
className = '',
title,
showReturn = true,
rightContent,
onReturn,
}) => {
const onClickBack = () => {
if (onReturn) {
onReturn();
} else {
dispatch(routerRedux.goBack());
}
};
return (
<div className={`${styles.header} ${className}`}>
<div
onClick={onClickBack}
className={styles.left}
>
{showReturn && <Icon type="left" />}
</div>
<div className={styles.title}>
{title}
</div>
<div className={styles.right}>
{rightContent}
</div>
</div>
);
};
export default Header;

View File

@@ -0,0 +1,48 @@
@import '../../global.less';
@action-size: 50px;
.header {
height: @header-height;
position: relative;
background-color: rgba(255, 255, 255, .8);
z-index: 10;
color: @primary-color;
user-select: none;
}
.left {
width: @action-size;
position: absolute;
left: 0;
top: 0;
z-index: 2;
text-align: center;
line-height: @header-height;
font-size: 1.5rem;
&:active {
background-color: @bg-color;
}
}
.title {
height: 100%;
position: relative;
width: 100%;
padding: 0 @action-size;
text-align: center;
line-height: @header-height;
z-index: 1;
font-size: 1.2rem;
font-weight: bold;
}
.right {
width: @action-size;
position: absolute;
right: 0;
top: 0;
z-index: 2;
text-align: center;
line-height: @header-height;
}

View File

@@ -0,0 +1,51 @@
import React from 'react';
import Label from '../Label';
import styles from './style.less';
export interface InfoProps {
className?: string;
title: string;
}
const Info: React.FC<InfoProps> = ({
className = '',
title,
children,
}) => {
return (
<div className={`${styles.header} ${className}`}>
<Label className="group-title" name={title} />
<div>
{children}
</div>
</div>
);
};
export interface InfoItemProps {
className?: string;
title: string | React.ReactNode;
info: string | React.ReactNode;
/**
* info 内容的主色调
*/
type?: 'normal' | 'primary' | 'secondary';
}
export const InfoItem: React.FC<InfoItemProps> = ({
className = '',
title,
info,
type = 'normal',
}) => {
return (
<div className={`${className} ${styles.item}`}>
<div className="left">{title}</div>
<div className={`right ${type}`}>{info}</div>
</div>
);
};
export default Info;

View File

@@ -0,0 +1,39 @@
@import '../../global.less';
.header {
width: 100%;
:global {
.group-title {
padding: 14px 10px 6px;
margin: 0;
}
}
}
.item {
width: 100%;
padding: 6px 20px;
display: flex;
flex-direction: row;
align-items: center;
:global {
.left {
flex-shrink: 0;
}
.right {
flex-grow: 1;
text-align: right;
&.primary {
color: @primary-color;
}
&.secondary {
color: @secondary-color;
}
}
}
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
import styles from './style.less';
export interface LabelProps {
className?: string;
name: string;
}
const Label: React.FC<LabelProps> = ({
className = '',
name,
}) => (
<div className={`${styles.label} ${className}`}>
<div className="indicator" />
<span className="name">{name}</span>
</div>
);
export default Label;

View File

@@ -0,0 +1,24 @@
.label {
font-size: 1rem;
display: flex;
flex-direction: row;
align-items: center;
margin: 10px 0;
:global {
.indicator {
display: inline-block;
width: 4px;
height: 0.8rem;
border-radius: 2px;
background-color: @primary-color;
}
.name {
display: inline-block;
font-weight: lighter;
margin-left: 8px;
}
}
}

View File

@@ -0,0 +1,267 @@
import React, { useRef, useState, useCallback, useEffect } from 'react';
import {
VariableSizeList,
ListChildComponentProps,
ListOnScrollProps,
} from 'react-window';
import AutoSizer, { Size } from 'react-virtualized-auto-sizer';
import { Icon } from 'antd';
import { formatMessage } from 'umi-plugin-locale';
import styles from './style.less';
import { connect } from 'dva';
const listPosition: { [key: string]: number } = {};
type ActionCallback<T = any> = () => T | Promise<T>;
export interface ListProps<T = any> {
className?: string;
distance?: number;
refreshDistance?: number;
loadDistance?: number;
itemSize?: number;
pullText?: string;
releaseText?: string;
refreshText?: string;
loadingText?: string;
loadFinishedText?: string;
refreshing?: boolean;
enableRefresh?: boolean;
loading?: boolean;
enableLoading?: boolean;
data?: T[];
onRefresh?: ActionCallback;
onLoadMore?: ActionCallback;
renderItem?: (item: T, index: number, array: T[]) => React.ReactNode;
itemKey?: (data: T) => string;
}
interface Point {
x: number;
y: number;
}
const List: React.FC<ListProps> = ({
className = '',
distance = 120,
refreshDistance = 80,
loadDistance = 200,
itemSize = 80,
pullText = 'common.pullToRefresh',
releaseText = 'common.releaseToRefresh',
refreshText = 'common.refreshing',
loadingText = 'common.loading',
loadFinishedText = 'common.loadFinished',
refreshing = false,
loading = false,
enableRefresh = true,
enableLoading = true,
data = [],
renderItem,
itemKey,
onRefresh,
onLoadMore,
...props
}) => {
if (refreshDistance > distance) {
throw Error('refreshDistance 应该小于 distance');
}
const scrollOffset = useRef(0);
const touchEnable = useRef(false);
const startPoint = useRef<Point>();
const scrollContainer = useRef<VariableSizeList>();
const [pullOffset, setPullOffset] = useState(0);
const validToRefresh = pullOffset >= refreshDistance;
const key = (props as any).location.pathname;
useEffect(() => {
if (refreshing && loading) {
throw Error('refreshing 与 loading 不能同时进行');
}
if (refreshing === loading) { // 同时为 false
setPullOffset(0);
} else {
setPullOffset(refreshing ? refreshDistance : 0);
}
}, [refreshing, refreshDistance, loading]);
useEffect(() => {
const { current } = scrollContainer;
if (current) {
current.resetAfterIndex(0, false);
}
return () => { listPosition[key] = scrollOffset.current; }
}, [data.length]);
const attachContainer = useCallback((node: VariableSizeList) => {
if (node) {
scrollContainer.current = node;
const position = listPosition[key];
if (typeof position === 'number') {
node.scrollTo(position);
}
}
}, []);
const onScroll = useCallback((e: ListOnScrollProps) => {
const { current } = scrollContainer;
scrollOffset.current = e.scrollOffset;
if (
enableLoading && !loading && !refreshing && current &&
data.length * itemSize - (+current.props.height) - e.scrollOffset < loadDistance
) {
onLoadMore && onLoadMore();
}
}, [loading, data.length, itemSize, enableLoading, key]);
const onTouchStart = useCallback((e: React.TouchEvent) => {
if (!enableRefresh || refreshing || loading) return;
const touch = e.touches[0];
if (!touch || scrollOffset.current) return;
startPoint.current = { x: touch.clientX, y: touch.clientY };
}, [enableRefresh, refreshing, loading]);
const onTouchEnd = useCallback(() => {
if (!enableRefresh || refreshing || loading) return;
touchEnable.current = false;
startPoint.current = undefined;
if (validToRefresh) {
onRefresh && onRefresh();
} else {
if (pullOffset !== 0) {
setPullOffset(0);
}
}
}, [pullOffset, validToRefresh, refreshing, enableRefresh, loading]);
const onTouchMove = useCallback((e: React.TouchEvent) => {
if (!enableRefresh || refreshing) return;
const touch = e.touches[0];
const start = startPoint.current;
if (!touch || !start) return;
if (!touchEnable.current) {
const deltaX = Math.abs(touch.clientX - start.x);
const deltaY = Math.abs(touch.clientY - start.y);
const yDirection = deltaY !== 0 && deltaX / deltaY < 0.5;
const enable = yDirection && scrollOffset.current === 0;
touchEnable.current = enable;
if (!enable) {
onTouchEnd();
}
return;
}
if (touchEnable.current && e.touches[0]) {
const { clientY } = e.touches[0];
const offset = clientY - start.y;
if (offset > 0) {
const variable = offset / distance;
if (variable <= Math.PI / 2) {
const result = distance * Math.sin(offset / distance);
setPullOffset(result);
}
}
}
}, [enableRefresh, distance]);
// 每一行的渲染
const Row: React.FC<ListChildComponentProps> = useCallback(({ style, index }) => {
let child;
if (index === data.length) {
child = (
<div className={styles.loadcontent}>
{loading && <Icon className="logo" type="loading" spin={true} />}
<span>
{formatMessage({ id: loading ? loadingText : loadFinishedText })}
</span>
</div>
);
} else {
child = renderItem && renderItem(data[index], index, data);
}
return (
<div className={styles.item} style={style}>
{child}
</div>
);
}, [data, loading]);
const calcItemSize = useCallback((index: number) => {
if (!refreshing && index === data.length) return 40;
return itemSize;
}, [data, refreshing]);
const keyGenerator = useCallback((index: number) => {
if (itemKey) {
const item = data[index];
return item ? itemKey(item) : 'loader';
}
return `${index}`;
}, [data, itemKey]);
// 列表内容
const ActualList: React.FC<Size> = useCallback(({ width, height }) => (
<VariableSizeList
ref={attachContainer}
style={{
transform: `translateY(${pullOffset}px)`,
transition: pullOffset === 0 || refreshing || loading ? 'all .2s' : undefined,
touchAction: refreshing ? 'none' : 'unset',
}}
className={styles.listcontent}
itemCount={data.length + (refreshing ? 0 : 1)}
itemSize={calcItemSize}
height={height}
width={width}
onScroll={onScroll}
itemKey={keyGenerator}
>
{Row}
</VariableSizeList>
), [pullOffset, data, itemSize, Row, refreshing]);
let pullContent = validToRefresh ? releaseText : pullText;
if (refreshing) {
pullContent = refreshText;
}
const RefreshContent = (
<div
style={{
transform: `translateY(${pullOffset / 3}px)`,
transition: pullOffset === 0 || refreshing ? 'all .2s' : undefined,
}}
className={styles.pullcontent}
>
{refreshing ? (
<Icon type="loading" className="loading" spin={true} />
) : (
<Icon
type="arrow-down"
className={`logo ${validToRefresh ? 'active' : ''}`}
/>
)}
<span className="desc">{formatMessage({ id: pullContent })}</span>
</div>
);
return (
<div
className={`${styles.container} ${className}`}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
>
{RefreshContent}
<AutoSizer>
{ActualList}
</AutoSizer>
</div>
);
}
export default connect(
({ router } : any) => ({ ...router }),
)(List);

View File

@@ -0,0 +1,62 @@
@import '../../global.less';
.item {
height: 60px;
}
.container {
width: 100%;
height: auto;
position: relative;
overflow: hidden;
background-color: @bg-color;
}
.pullcontent {
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
color: @hint-color;
:global {
.logo, .desc {
transition: all .3s;
}
.logo, .loading {
margin-right: 6px;
}
.logo {
&.active {
transform: rotateZ(180deg);
}
}
}
}
.loadcontent {
background-color: @bg-color;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: @hint-color;
:global {
.logo {
margin-right: 6px;
}
}
}
.listcontent {
background-color: #fff;
position: relative;
z-index: 1;
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { Spin } from 'antd'; // loading components from code split
// https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
const PageLoading = () => (
<div
style={{
paddingTop: 100,
textAlign: 'center',
}}
>
<Spin size="large" />
</div>
);
export default PageLoading;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { formatMessage } from 'umi-plugin-locale';
import { Icon } from 'antd';
import styles from './style.less';
export interface SettingItemProps {
logo: string;
title: string;
onClick?: () => any;
className?: string;
}
const SettingItem: React.FC<SettingItemProps> = ({
logo,
title,
onClick,
className = '',
}) => {
const msg = formatMessage({ id: title });
return (
<div className={`${styles.item} ${className}`} onClick={onClick}>
<img className={styles.logo} src={logo} alt={msg} />
<span className={styles.title}>{msg}</span>
<Icon className={styles.go} type="right" />
</div>
);
};
export default SettingItem;

View File

@@ -0,0 +1,34 @@
@import '../../global.less';
.item {
background-color: #fff;
padding: 10px 20px;
border: 1px solid lighten(@primary-color, 40%);
display: flex;
flex-direction: row;
align-items: center;
transition: background-color .2s;
user-select: none;
border-radius: 8px;
&:active {
background-color: @bg-color;
}
}
.logo {
flex-shrink: 0;
width: 24px;
}
.title {
flex-grow: 1;
margin: 0 10px;
}
.go {
flex-shrink: 0;
color: lightgrey;
font-size: 1.2rem;
}

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { formatMessage } from 'umi-plugin-locale';
import styles from './style.less';
export interface TabItemModel {
key: string;
name: string;
icon?: string;
iconSelected?: string;
}
export type TabItemModels = TabItemModel[];
export interface TabProps {
items: TabItemModels;
selectedKey?: string;
onChange?: (key: string) => void;
}
const Tab: React.FC<TabProps> = ({
items,
selectedKey,
onChange,
}) => {
const onItemClick = (key: string) => {
if (onChange) {
onChange(key);
}
};
const itemWidth = { width: `${100 / (items.length || 1)}%` };
return (
<div className={styles.tab}>
{items.map(item => (
<div
onClick={() => onItemClick(item.key)}
style={itemWidth}
className={`${styles.item} ${item.key === selectedKey ? styles.active : ''}`}
key={item.key}
>
{item.icon && item.iconSelected && <img src={item.key === selectedKey ? item.iconSelected : item.icon} />}
<span>{formatMessage({ id: item.name })}</span>
</div>
))}
</div>
)
};
export default Tab;

View File

@@ -0,0 +1,33 @@
@import '../../global.less';
.tab {
width: 100%;
height: 100%;
user-select: none;
}
.item {
height: 100%;
transition: all .18s;
display: inline-flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 0.6rem;
color: @hint-color;
:global {
img {
@size: 20px;
width: @size;
margin-bottom: 4px;
}
}
}
.active {
color: @primary-color;
transform: scale(1.15);
}

29
src/global.less Normal file
View File

@@ -0,0 +1,29 @@
html, body, #root {
height: 100%;
font-size: 14px;
}
body {
margin: 0;
}
@primary-color: #3A3A38;
@secondary-color: #3A3A38;
@bg-color: #EFEFEF;
@bg-primary-color: #ffded8;
@hint-color: gray;
@header-height: 54px;
.ant-btn {
// border-radius: 12px!important;
&.ant-btn-primary.gradient {
border-width: 0;
background: linear-gradient(to right, @primary-color, @secondary-color);
}
}
.ant-modal-centered::before {
margin-top: -25%;
}

View File

@@ -0,0 +1,16 @@
import 'jest';
import BasicLayout from '..';
import React from 'react';
import renderer, { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
describe('Layout: BasicLayout', () => {
it('Render correctly', () => {
const wrapper: ReactTestRenderer = renderer.create(<BasicLayout />);
expect(wrapper.root.children.length).toBe(1);
const outerLayer = wrapper.root.children[0] as ReactTestInstance;
expect(outerLayer.type).toBe('div');
const title = outerLayer.children[0] as ReactTestInstance;
expect(title.type).toBe('h1');
expect(title.children[0]).toBe('Yay! Welcome to umi!');
});
});

24
src/layouts/index.css Normal file
View File

@@ -0,0 +1,24 @@
.normal {
height: 100%;
}
.splash {
height: 100%;
display: flex;
flex-direction: column;
}
.centered {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 1.6rem;
}
.bottom {
padding: 40px;
text-align: center;
}

37
src/layouts/index.tsx Normal file
View File

@@ -0,0 +1,37 @@
import React, { useEffect } from 'react';
import { useTimeout } from 'react-use';
import { formatMessage } from 'umi-plugin-locale';
import { isDev } from '@/utils/utils';
import splash from '@/assets/splash.png';
import styles from './index.css';
const BasicLayout: React.FC = props => {
const [isReady, cancel] = useTimeout(isDev ? 100 : 1500);
useEffect(() => {
return () => {
if (!isReady()) cancel();
};
}, []);
if (!isReady()) {
return (
<div className={styles.splash}>
<div className={styles.centered}>
<span>{formatMessage({ id: 'index.start1' })}</span>
<span>{formatMessage({ id: 'index.start2' })}</span>
</div>
<span className={styles.bottom}>{formatMessage({ id: 'app.name' })}</span>
</div>
);
}
return (
<div className={styles.normal}>
{props.children}
</div>
);
};
export default BasicLayout;

80
src/locales/en-US.ts Normal file
View File

@@ -0,0 +1,80 @@
export default {
'app.name': '第五维度',
'index.start': 'Splash Screen',
'index.start1': '数字货币',
'index.start2': '财富管理平台',
'network.error': 'Network error',
'common.phoneHolder': 'Please input phone number',
'common.pwdHolder': 'Please input password',
'common.next': 'Next',
'common.pullToRefresh': 'Pull to refresh',
'common.releaseToRefresh': 'Release to refresh',
'common.refreshing': 'Refreshing',
'common.loading': 'Loading',
'common.loadFinished': 'Finished',
'common.cancel': 'Cancel',
'common.ok': 'Ok',
'check.login': 'Sign In',
'check.register': 'Sign Up',
'check.forget': 'Forget?',
'check.reset': 'Reset Password',
'check.msgArea': 'Please select area',
'check.msgPhone': 'Please input correct phone number',
'check.msgPwd': '请输入正确的密码',
'home.tabHome': 'Home',
'home.tabEstate': '矿场',
'home.tabFund': 'Fund',
'home.tabMarket': '社区',
'home.tabProfile': 'Profile',
'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: ',
'estate.tab1': '买卖记录',
'estate.tab2': '地块介绍',
'estate.title1': '买入信息',
'estate.title2': '卖出信息',
'estate.inPrice': '买入价格',
'estate.expectProfit': '预期收益',
'estate.inTime': '买入时间',
'estate.lockTime': '到期时间',
'estate.outPrice': '卖出价格',
'estate.taxfee': '税费',
'estate.actualProfit': '实际收益',
'estate.outTime': '卖出时间',
'profile.account': 'Account Settings',
'profile.system': 'System Settings',
'profile.identity': '实名认证',
'profile.share': '社区推广',
'profile.msg': 'Message Center',
'profile.about': 'About',
'account.title': 'My Account',
'account.nickname': 'Nickname',
'account.phone': 'Phone',
'account.email': 'Email',
'account.loginPwd': 'Login password',
'account.fundPwd': 'Fund password',
'account.factor': '2FA',
'account.logout': 'Logout',
'account.confirm': 'Are you sure to logout?',
'system.title': 'Settings',
'system.language': 'Language',
'system.currency': 'Currency',
'system.chooseLang': 'Choose Language',
}

80
src/locales/zh-CN.ts Normal file
View File

@@ -0,0 +1,80 @@
export default {
'app.name': '第五维度',
'index.start': '启动页',
'index.start1': '数字货币',
'index.start2': '财富管理平台',
'network.error': '网络错误',
'common.phoneHolder': '请输入手机号',
'common.pwdHolder': '请输入密码',
'common.next': '下一步',
'common.pullToRefresh': '下拉刷新',
'common.releaseToRefresh': '释放以刷新',
'common.refreshing': '刷新中',
'common.loading': '加载中',
'common.loadFinished': '已经到底了~',
'common.cancel': '取消',
'common.ok': '确定',
'check.login': '登录',
'check.register': '注册',
'check.forget': '忘记密码?',
'check.reset': '重置密码',
'check.msgArea': '请选择区号',
'check.msgPhone': '请输入正确的手机号',
'check.msgPwd': '请输入正确的密码',
'home.tabHome': '首页',
'home.tabEstate': '矿场',
'home.tabFund': '资金',
'home.tabMarket': '社区',
'home.tabProfile': '我的',
'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': '预期收益:',
'estate.tab1': '买卖记录',
'estate.tab2': '地块介绍',
'estate.title1': '买入信息',
'estate.title2': '卖出信息',
'estate.inPrice': '买入价格',
'estate.expectProfit': '预期收益',
'estate.inTime': '买入时间',
'estate.lockTime': '到期时间',
'estate.outPrice': '卖出价格',
'estate.taxfee': '税费',
'estate.actualProfit': '实际收益',
'estate.outTime': '卖出时间',
'profile.account': '账户管理',
'profile.system': '系统设置',
'profile.identity': '实名认证',
'profile.share': '社区推广',
'profile.msg': '消息中心',
'profile.about': '关于洛格',
'account.title': '我的账户',
'account.nickname': '昵称',
'account.phone': '手机号',
'account.email': '邮箱',
'account.loginPwd': '登录密码',
'account.fundPwd': '资金密码',
'account.factor': '双因子验证2FA',
'account.logout': '退出登录',
'account.confirm': '是否确认退出登录?',
'system.title': '我的设置',
'system.language': '语言偏好',
'system.currency': '法币偏好',
'system.chooseLang': '请选择语言',
}

31
src/models/ext.ts Normal file
View File

@@ -0,0 +1,31 @@
import { Model } from 'dva';
import { Estate } from '@/types/common';
export interface ExtModelState {
/**
* 进入地产详情时的中转
*/
estateExt?: Estate;
}
const model: Model = {
namespace: 'ext',
state: {
estateExt: { id: 'xjlajlkjkdf', name: '巴中市' },
} as ExtModelState,
effects: {
},
reducers: {
onUpdateState(state, { payload }: any) {
return {
...state,
...payload,
};
}
},
};
export default model;

78
src/models/user.ts Normal file
View File

@@ -0,0 +1,78 @@
import { Model } from 'dva';
import _ from 'lodash';
import { getLocale, setLocale } from 'umi-plugin-locale'
import { loadUser, storeUser, clearUser } from '@/utils/storage';
import { User } from '@/types/common';
const initState = () => {
const user = loadUser();
return {
user,
isLogin: Boolean(user && user.id),
lang: getLocale(),
};
}
export interface UserModelState {
user?: User;
isLogin: boolean;
lang: string;
}
const model: Model = {
namespace: 'user',
state: initState() as UserModelState,
effects: {
*updateUser({ payload }, { put, select }) {
if (!payload) return;
const { user }: UserModelState = yield select((state: any) => state.user);
if (!_.isEqual(payload, user)) {
const allNew = { ...(user || {}), ...payload };
yield put({ type: 'onSaveUser', payload: allNew });
storeUser(allNew);
}
},
*clearUser(_, { put }) {
yield put({ type: 'onSaveUser' });
clearUser();
},
*updateToken({ payload: token }, { put }) {
yield put({ type: 'updateUser', payload: { token } });
},
*logout(_, { put, all }) {
yield all([
put({ type: 'clearUser' }),
put({ type: 'home/clear' }),
]);
},
*changeLang({ payload: lang }, { put }) {
setLocale(lang);
yield put({ type: 'onUpdateState', payload: { lang } });
},
},
reducers: {
onUpdateState(state, { payload }: any) {
return {
...state,
...payload,
};
},
onSaveUser(state, { payload }: any) {
return {
...state,
user: payload,
isLogin: Boolean(payload && payload.id),
};
}
},
};
export default model;

View File

@@ -0,0 +1 @@
export const formatMessage = (): string => 'Mock text';

View File

@@ -0,0 +1,21 @@
import 'jest';
import Index from '..';
import React from 'react';
import renderer, { ReactTestInstance, ReactTestRenderer } from 'react-test-renderer';
jest.mock('umi-plugin-locale');
describe('Page: index', () => {
it('Render correctly', () => {
const wrapper: ReactTestRenderer = renderer.create(<Index />);
expect(wrapper.root.children.length).toBe(1);
const outerLayer = wrapper.root.children[0] as ReactTestInstance;
expect(outerLayer.type).toBe('div');
expect(outerLayer.children.length).toBe(2);
const getStartLink = outerLayer.findAllByProps({
href: 'https://umijs.org/guide/getting-started.html',
}) as ReactTestInstance[];
expect(getStartLink.length).toBe(1);
expect(getStartLink[0].children).toMatchObject(['Mock text']);
});
});

View File

@@ -0,0 +1,77 @@
/**
* Title: 地产详情
* Routes:
* - ./src/pages/routes
*/
import React from 'react';
import { connect, DispatchProp } from 'dva';
import { formatMessage } from 'umi-plugin-locale';
import { Button, Modal } from 'antd';
import Header from '@/components/Header';
import AccountItem from '@/components/AccountItem';
import { UserModelState } from '@/models/user';
import styles from './style.less';
const AccountPage: React.FC<DispatchProp & UserModelState> = ({
dispatch,
user,
}) => {
const onLogout = () => {
Modal.confirm({
title: formatMessage({ id: 'account.confirm' }),
onOk: () => dispatch({ type: 'user/logout' }),
okText: formatMessage({ id: 'common.ok' }),
cancelText: formatMessage({ id: 'common.cancel' }),
centered: true,
});
};
return (
<div className={styles.container}>
<Header
className={styles.header}
title={formatMessage({ id: 'account.title' })}
/>
{user && <div className={styles.content}>
<AccountItem
className="group"
title="account.nickname"
message={user.nickname}
showGo={false}
/>
<AccountItem
className="group"
title="account.phone"
message={user.phone}
/>
<AccountItem
title="account.email"
message={user.email}
/>
<AccountItem
className="group"
title="account.loginPwd"
/>
<AccountItem
title="account.fundPwd"
/>
<AccountItem
title="account.factor"
/>
<Button
className={styles.logout}
size="large"
onClick={onLogout}
>
{formatMessage({ id: 'account.logout' })}
</Button>
</div>}
</div>
);
};
export default connect(
({ user }: any) => ({ ...user }),
)(AccountPage);

View File

@@ -0,0 +1,30 @@
@import '../../global.less';
.container {
height: 100%;
display: flex;
flex-direction: column;
}
.header {
flex-shrink: 0;
}
.content {
background-color: @bg-color;
flex-grow: 1;
overflow: auto;
display: flex;
flex-direction: column;
align-items: stretch;
:global {
.group {
margin-top: 8px;
}
}
}
.logout {
margin: 16px 10px;
}

47
src/pages/check/check.tsx Normal file
View File

@@ -0,0 +1,47 @@
import React from 'react';
import { Button, Input } from 'antd';
import { connect, DispatchProp } from 'dva';
import { formatMessage } from 'umi-plugin-locale';
import { CheckModelState } from './model';
import AreaSelection from '@/components/AreaSelection';
const CheckPage: React.FC<DispatchProp & CheckModelState> = ({
areacodes,
areacode,
phone,
submitting,
dispatch,
}) => {
return (
<>
<AreaSelection
className="item"
areacode={areacode}
areacodes={areacodes}
onChange={areacode => dispatch({ type: 'check/onUpdateState', areacode })}
size="large"
/>
<Input
className="item"
placeholder={formatMessage({ id: 'common.phoneHolder' })}
value={phone}
onChange={e => dispatch({ type: 'check/onUpdatePhone', payload: e.target.value })}
size="large"
type="tel"
/>
<Button
className="item gradient"
type="primary"
loading={submitting}
onClick={() => dispatch({ type: 'check/submitCheck' })}
size="large"
>{formatMessage({ id: 'common.next' })}</Button>
</>
);
}
export default connect(
({ check } : any) => ({ ...check })
)(CheckPage) as any;

70
src/pages/check/index.tsx Normal file
View File

@@ -0,0 +1,70 @@
/**
* Routes:
* - ./src/pages/routes
*/
import React, { useEffect } from 'react';
import { Spin } from 'antd';
import { connect, DispatchProp } from 'dva';
import FittedImage from 'react-fitted-image';
import { CheckModelState, PageStep } from './model';
import { PageState } from '@/types/common';
import Header from '@/components/Header';
import bgPic from '@/assets/bg_login.png';
import styles from './style.less';
import CheckPage from './check';
import LoginPage from './login';
import RegisterPage from './register';
import { formatMessage } from 'umi-plugin-locale';
const Page: React.FC<DispatchProp & CheckModelState> = ({
pageState,
pageStep,
dispatch,
}) => {
useEffect(() => {
dispatch({ type: 'check/load' });
}, []);
if (pageState === PageState.Pending) {
return (
<div className={styles.loading}><Spin /></div>
);
}
if (pageState === PageState.Failure) {
return (
<div onClick={() => dispatch({ type: 'check/load' })}></div>
);
}
return (
<div className={styles.check}>
{
pageStep !== PageStep.Check &&
<Header
className={styles.header}
onReturn={() => dispatch({ type: 'check/back' })}
/>
}
<div className="cover">
<div className="image">
<FittedImage fit="contain" src={bgPic} />
</div>
<span>{formatMessage({ id: 'app.name' })}</span>
</div>
{{
[PageStep.Check]: <CheckPage />,
[PageStep.Login]: <LoginPage />,
[PageStep.Register]: <RegisterPage />,
[PageStep.Reset]: null,
[PageStep.ResetSuccess]: null,
}[pageStep]}
</div>
);
}
export default connect(
({ check } : any) => ({ ...check })
)(Page);

36
src/pages/check/login.tsx Normal file
View File

@@ -0,0 +1,36 @@
import React from 'react';
import { Button, Input } from 'antd';
import { connect, DispatchProp } from 'dva';
import { formatMessage } from 'umi-plugin-locale';
import { CheckModelState } from './model';
const LoginPage: React.FC<DispatchProp & CheckModelState> = ({
password,
submitting,
dispatch,
}) => {
return (
<>
<Input
className="item"
placeholder={formatMessage({ id: 'common.pwdHolder' })}
value={password}
onChange={e => dispatch({ type: 'check/onUpdatePassword', payload: e.target.value })}
size="large"
type="password"
/>
<Button
className="item gradient"
type="primary"
loading={submitting}
onClick={() => dispatch({ type: 'check/submitLogin' })}
size="large"
>{formatMessage({ id: 'check.login' })}</Button>
</>
);
}
export default connect(
({ check } : any) => ({ ...check })
)(LoginPage) as any;

164
src/pages/check/model.ts Normal file
View File

@@ -0,0 +1,164 @@
import { Model } from "dva";
import _ from 'lodash';
import { message } from 'antd';
import { delay } from 'dva/saga';
import { PageState, AreaCode, AreaCodes } from '@/types/common';
import { getAreaCodes, getUserInfo } from '@/services/common';
import { validPhoneInput, validPwdInput, validPhone, validPwd } from '@/utils/validate';
import { checkExistence, CheckResult, signIn } from './service';
import { formatMessage } from 'umi-plugin-locale';
export enum PageStep {
Check = 'check',
Login = 'login',
Register = 'register',
Reset = 'reset',
ResetSuccess = 'resetSuccess',
}
export interface CheckModelState {
pageState: PageState,
pageStep: PageStep,
areacodes: AreaCodes,
areacode: AreaCode | undefined, // 选中的地区
phone: string;
password: string;
submitting: boolean;
}
const model: Model = {
namespace: 'check',
state: {
pageState: PageState.Pending,
pageStep: PageStep.Check,
areacodes: [],
areacode: undefined,
phone: '',
password: '',
submitting: false,
} as CheckModelState,
effects: {
*back(_, { select, put }) {
const { pageStep: prev } = yield select((state: any) => state.check);
let pageStep = PageStep.Check;
switch (prev) {
case PageStep.Login:
case PageStep.Register:
case PageStep.ResetSuccess:
pageStep = PageStep.Check;
break;
case PageStep.Reset:
pageStep = PageStep.Login;
break;
}
yield put({ type: 'onUpdateState', pageStep });
},
*load(_, { call, put, all }) {
yield put({ type: 'onUpdatePageState', payload: PageState.Pending });
yield delay(800);
try {
const areacodes = yield call(getAreaCodes);
yield all([
put({ type: 'onUpdatePageState', payload: PageState.Success }),
put({ type: 'onUpdateAreaCodes', payload: areacodes }),
]);
} catch(e) {
yield put({ type: 'onUpdatePageState', payload: PageState.Failure });
}
},
*submitCheck(_, { put, call, select }) {
const { areacode, phone } = yield select((state: any) => state.check);
if (!areacode) return message.warn(formatMessage({ id: 'check.msgArea' }));
if (!validPhone(phone)) return message.warn(formatMessage({ id: 'check.msgPhone' }));
yield put({ type: 'onUpdateState', submitting: true });
try {
const checkResult: CheckResult = yield call(checkExistence, areacode, phone);
yield put({
type: 'onUpdateState',
pageStep: {
[CheckResult.Login]: PageStep.Login,
[CheckResult.Register]: PageStep.Register,
}[checkResult],
});
} catch(e) {
message.error(e.message);
} finally {
yield put({ type: 'onUpdateState', submitting: false });
}
},
*submitLogin(_, { put, call, select }) {
const { areacode, phone, password } = yield select((state: any) => state.check);
if (!areacode) return message.warn(formatMessage({ id: 'check.msgArea' }));
if (!validPhone(phone)) return message.warn(formatMessage({ id: 'check.msgPhone' }));
if (!validPwd(password)) return message.warn(formatMessage({ id: 'check.msgPwd' }))
yield put({ type: 'onUpdateState', submitting: true });
try {
const token = yield call(signIn, areacode, phone, password);
yield put({ type: 'user/updateToken', payload: token });
const user = yield call(getUserInfo, true);
yield put({ type: 'user/updateUser', payload: user });
} catch(e) {
message.error(e.message);
} finally {
yield put({ type: 'onUpdateState', submitting: false });
}
},
*submitRegister(_, { put }) {
yield put({ type: 'onUpdateState', submitting: true });
yield delay(800);
yield put({ type: 'onUpdateState', submitting: false });
yield put({ type: 'onUpdateState', pageStep: PageStep.Check });
},
},
reducers: {
onUpdatePageState(state, { payload: pageState } : any) {
return {
...state,
pageState,
};
},
onUpdateAreaCodes(state, { payload: areacodes } : any) {
return {
...state,
areacodes,
areacode: areacodes[0],
};
},
onUpdatePhone(state, { payload } : any) {
const phone = validPhoneInput(payload) ? payload : state.phone;
return {
...state,
phone,
};
},
onUpdatePassword(state, { payload } : any) {
const password = validPwdInput(payload) ? payload : state.password;
return {
...state,
password,
};
},
onUpdateState(state, newState) {
return {
...state,
...newState,
};
},
},
};
export default model;

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { Button, Input } from 'antd';
import { connect, DispatchProp } from 'dva';
import { formatMessage } from 'umi-plugin-locale';
import { CheckModelState } from './model';
const RegisterPage: React.FC<DispatchProp & CheckModelState> = ({
password,
submitting,
dispatch,
}) => {
return (
<>
<Input
className="item"
placeholder={formatMessage({ id: 'common.pwdHolder' })}
value={password}
onChange={e => dispatch({ type: 'check/onUpdatePassword', payload: e.target.value })}
size="large"
type="password"
/>
<Button
className="item gradient"
type="primary"
loading={submitting}
onClick={() => dispatch({ type: 'check/submitRegister' })}
size="large"
>{formatMessage({ id: 'check.register' })}</Button>
</>
);
}
export default connect(
({ check } : any) => ({ ...check })
)(RegisterPage) as any;

View File

@@ -0,0 +1,30 @@
import http from '@/utils/http';
import { AreaCode } from '@/types/common';
import { encodePhone } from '@/utils/transform';
export enum CheckResult {
Login = 0,
Register = 1,
}
export async function checkExistence(areacode: AreaCode, phone: string): Promise<CheckResult> {
const { code, msg } = await http.get('/sign/check', { phone: encodePhone(areacode, phone) });
if (code !== CheckResult.Login && code !== CheckResult.Register) {
throw Error(msg);
}
return code as CheckResult;
}
export async function signIn(areacode: AreaCode, phone: string, password: string): Promise<string> {
const { ok, msg, data } = await http.post(
'/sign/in',
{
phone: encodePhone(areacode, phone),
password,
},
);
if (!ok || !data || !data.token) {
throw Error(msg);
}
return data.token;
}

View File

@@ -0,0 +1,49 @@
.check {
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
:global {
.item {
margin: 10px 5% 0;
width: auto;
}
.ant-btn {
margin-top: 30px;
}
.cover {
height: 40vh;
transition: height .2s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: height .2s;
}
.image {
width: 30%;
margin-bottom: 20px;
// width: 100%;
}
}
}
.header {
position: absolute;
top: 0;
left: 0;
width: 100%;
background-color: #fff0;
}
.loading {
height: 100%;
text-align: center;
line-height: 30;
line-height: 80vh;
}

150
src/pages/estate/index.tsx Normal file
View File

@@ -0,0 +1,150 @@
/**
* Title: 地产详情
* Routes:
* - ./src/pages/routes
*/
import React, { useEffect } from 'react';
import { connect, DispatchProp } from 'dva';
import { Spin, Tabs } from 'antd';
import { formatMessage } from 'umi-plugin-locale';
import FittedImage from 'react-fitted-image';
import { EstateModelState, PageTab } from './model';
import { ExtModelState } from '@/models/ext';
import { PageState, EstateType } 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 EstatePage: React.FC<DispatchProp & EstateModelState & ExtModelState> = ({
dispatch,
tab,
estate,
estateExt,
pageState,
}) => {
useEffect(() => {
dispatch({ type: 'estate/load', payload: estateExt });
return () => {
dispatch({ type: 'estate/clear' });
};
}, []);
if (!estateExt) return null;
let body = null;
if (pageState === PageState.Failure) {
body = (
<div className={styles.loading}>
<Failure />
</div>
);
}
if (pageState === PageState.Pending) {
body = (
<div className={styles.loading}><Spin /></div>
);
}
if (pageState === PageState.Success && estate) {
const image = (
<FittedImage
className="image"
src={estate.image || ''}
fit="cover"
/>
);
const record = (
<div>
<Info title={formatMessage({ id: 'estate.title1' })}>
<InfoItem
title={formatMessage({ id: 'estate.inPrice' })}
info={`${estate.inPrice} LOG`}
type="primary"
/>
<InfoItem
title={formatMessage({ id: 'estate.expectProfit' })}
info={estate.profitShow}
type="primary"
/>
<InfoItem
title={formatMessage({ id: 'estate.inTime' })}
info={estate.inTime}
type="primary"
/>
<InfoItem
title={formatMessage({ id: 'estate.lockTime' })}
info={estate.lockTime}
type="primary"
/>
</Info>
{estate.type === EstateType.Sold &&
<Info title={formatMessage({ id: 'estate.title2' })}>
<InfoItem
title={formatMessage({ id: 'estate.outPrice' })}
info={`${estate.outPrice} LOG`}
type="secondary"
/>
<InfoItem
title={formatMessage({ id: 'estate.taxfee' })}
info={`${estate.taxfeeAmount} LOG`}
type="secondary"
/>
<InfoItem
title={formatMessage({ id: 'estate.actualProfit' })}
info={`${estate.profitPrice} LOG`}
type="secondary"
/>
<InfoItem
title={formatMessage({ id: 'estate.outTime' })}
info={estate.outTime}
type="secondary"
/>
</Info>
}
</div>
);
const detail = (
<div className="detail">{estate.desc}</div>
);
body = (
<div className={styles.content}>
<div className="banner">
{image}
</div>
<div className="banner overlay">
{image}
</div>
<Tabs
className="tab-indicator"
size="small"
activeKey={tab}
onChange={value => dispatch({ type: 'estate/onUpdateState', payload: { tab: value } })}
>
<Tabs.TabPane key={PageTab.Record} tab={formatMessage({ id: 'estate.tab1' })} />
<Tabs.TabPane key={PageTab.Detail} tab={formatMessage({ id: 'estate.tab2' })} />
</Tabs>
<div className="tab-content">
{tab === PageTab.Record ? record : detail}
</div>
</div>
);
}
return (
<div className={styles.estate}>
<Header
className={styles.header}
title={estateExt.name}
/>
<div className={styles.body}>{body}</div>
</div>
);
};
export default connect(
({ estate, ext }: any) => ({ ...estate, ...ext }),
)(EstatePage);

83
src/pages/estate/model.ts Normal file
View File

@@ -0,0 +1,83 @@
import { Model, routerRedux } from "dva";
import { message } from 'antd';
import { Estate, PageState } from '@/types/common';
import { getEstateDetail } from './service';
import { isDev } from '@/utils/utils';
export enum PageTab {
Record = '0',
Detail = '1',
}
export interface EstateModelState {
tab: PageTab.Record;
estate?: Estate;
pageState: PageState;
}
const initState: EstateModelState = {
tab: PageTab.Record,
estate: undefined,
pageState: PageState.Pending,
};
const model: Model = {
namespace: 'estate',
state: initState,
effects: {
*changeTab({ payload: tab }, { put }) {
yield put({ type: 'onUpdateState', payload: { tab } });
},
*load({ payload }, { put, call }) {
if (!payload) {
yield put(routerRedux.goBack());
return;
}
yield put({
type: 'onUpdateState',
payload: { pageState: PageState.Pending },
});
try {
const estate = yield call(getEstateDetail, payload.id);
if (payload.id === estate.id || isDev) { // TODO: Remember to remove this
yield put({
type: 'onUpdateState',
payload: {
pageState: PageState.Success,
estate,
tab: PageTab.Record,
},
});
}
} catch (e) {
message.error(e.message);
yield put({
type: 'onUpdateState',
payload: { pageState: PageState.Failure },
});
}
},
*clear(_, { put }) {
yield put({ type: 'onUpdateState', payload: initState });
if (!isDev) {
yield put({ type: 'ext/onUpdateState', payload: { estateExt: undefined } });
}
},
},
reducers: {
onUpdateState(state, { payload }: any) {
return {
...state,
...payload,
};
},
},
};
export default model;

View File

@@ -0,0 +1,18 @@
import http from '@/utils/http';
import { Estate, EstateType } from '@/types/common';
import { calcEstate } from '@/utils/transform';
export async function getEstateDetail(id: string) {
const {
ok,
data,
msg,
} = await http.get<Estate>('/estateorder/detail', { id });
if (ok && data) {
data.type = data.outTime ? EstateType.Sold : EstateType.Hold;
calcEstate(data);
return data;
}
throw Error(msg);
}

View File

@@ -0,0 +1,87 @@
@import '../../global.less';
.estate {
height: 100%;
position: relative;
}
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: rgba(255, 255, 255, .7);
}
.body {
height: 100%;
}
.loading {
height: 100%;
text-align: center;
line-height: 30;
line-height: 80vh;
}
.content {
height: 100%;
display: flex;
flex-direction: column;
position: relative;
:global {
@banner-height: 200px;
.banner {
flex-shrink: 0;
height: @banner-height;
width: 100%;
left: 0;
top: 0;
position: absolute;
.image {
height: @banner-height;
}
}
.overlay {
height: @header-height;
overflow: hidden;
filter: blur(6px);
}
.tab-indicator {
margin-top: @banner-height;
flex-shrink: 0;
.ant-tabs-bar {
margin: 0;
}
.ant-tabs-nav-scroll {
text-align: center;
}
}
.ant-tabs-tab-active, .ant-tabs-tab:active {
color: @secondary-color;
}
.ant-tabs-ink-bar {
background-color: @secondary-color;
}
.tab-content {
flex-grow: 1;
overflow: auto;
padding: 10px 0 20px;
line-height: 1.6;
:global {
.detail {
margin: 0 10px;
}
}
}
}
}

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;
}

7
src/pages/index.css Normal file
View File

@@ -0,0 +1,7 @@
.normal {
font-family: Georgia, sans-serif;
text-align: center;
line-height: 80vh;
}

13
src/pages/index.tsx Executable file
View File

@@ -0,0 +1,13 @@
/**
* Routes:
* - ./src/pages/routes
*/
import React from 'react';
import { Redirect } from 'umi'
import { UserModelState } from '@/models/user';
const Page: React.FC<UserModelState> = () => {
return <Redirect to="/home" />;
}
export default Page;

31
src/pages/routes.tsx Normal file
View File

@@ -0,0 +1,31 @@
import React, { useEffect } from 'react';
import { Redirect, RouterTypes } from 'umi'
import { connect } from 'dva';
import { UserModelState } from '@/models/user';
import styles from './index.css';
const unauthorizedPaths = [
'/check',
'/register',
];
const Page: React.FC<RouterTypes & UserModelState> = ({
isLogin,
children,
location: { pathname },
}) => {
if (pathname !== '/' && !isLogin && !unauthorizedPaths.includes(pathname)) {
return <Redirect to="/check" />
}
if (isLogin && unauthorizedPaths.includes(pathname)) {
return <Redirect to="/home" />;
}
return (
<>{children}</>
);
};
export default connect(
({ user }: any) => ({ ...user }),
)(Page);

View File

@@ -0,0 +1,70 @@
/**
* Title: 地产详情
* Routes:
* - ./src/pages/routes
*/
import React, { useState } from 'react';
import { connect, DispatchProp } from 'dva';
import { formatMessage } from 'umi-plugin-locale';
import Header from '@/components/Header';
import AccountItem from '@/components/AccountItem';
import ChoiceModal from '@/components/ChoiceModal';
import { UserModelState } from '@/models/user';
import { fromLangToText, supportedLangs } from '@/utils/transform';
import styles from './style.less';
const langs = supportedLangs.map(it => ({
key: it.lang,
value: it.text,
}));
const SystemPage: React.FC<DispatchProp & UserModelState> = ({
dispatch,
lang,
}) => {
const [langVisible, setLangVisible] = useState(false);
const onItemSelected = (key: string) => {
setLangVisible(false);
dispatch({ type: 'user/changeLang', payload: key });
};
const langModal = (
<ChoiceModal
closable={false}
data={langs}
selectedKey={lang}
onItemSelected={onItemSelected}
visible={langVisible}
onCancel={() => setLangVisible(false)}
title={formatMessage({ id: 'system.chooseLang' })}
/>
);
return (
<div className={styles.container}>
<Header
className={styles.header}
title={formatMessage({ id: 'system.title' })}
/>
<div className={styles.content}>
<AccountItem
className="group"
title="system.language"
preview={fromLangToText(lang)}
onClick={() => setLangVisible(true)}
/>
<AccountItem
className="group"
title="system.currency"
/>
</div>
{langModal}
</div>
);
};
export default connect(
({ user }: any) => ({ ...user }),
)(SystemPage);

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