Reinit project
16
.editorconfig
Executable 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
|
||||
22
.gitignore
vendored
Executable 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
@@ -0,0 +1,7 @@
|
||||
**/*.md
|
||||
**/*.svg
|
||||
**/*.ejs
|
||||
**/*.html
|
||||
package.json
|
||||
.umi
|
||||
.umi-production
|
||||
11
.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ".prettierrc",
|
||||
"options": { "parser": "json" }
|
||||
}
|
||||
]
|
||||
}
|
||||
43
.umirc.ts
Normal 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
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
0
mock/.gitkeep
Normal file
64
package.json
Normal 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
@@ -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
|
After Width: | Height: | Size: 24 KiB |
16
src/assets/estate.svg
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 4.8 KiB |
14
src/assets/fund.svg
Normal 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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 82 KiB |
18
src/assets/market.svg
Normal 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
@@ -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
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/assets/p_about.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/p_account.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/p_identity.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/p_msg.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/p_share.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/p_system.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
19
src/assets/profile.svg
Normal 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
@@ -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
|
After Width: | Height: | Size: 2.9 KiB |
35
src/components/AccountItem/index.tsx
Normal 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;
|
||||
43
src/components/AccountItem/style.less
Normal 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;
|
||||
}
|
||||
70
src/components/AreaSelection/index.tsx
Normal 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;
|
||||
15
src/components/AreaSelection/style.less
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
.component {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
:global {
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.name, .code {
|
||||
width: 48%;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/components/ChoiceModal/index.tsx
Normal 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;
|
||||
19
src/components/ChoiceModal/style.less
Normal 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;
|
||||
}
|
||||
3
src/components/Empty/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Empty } from 'antd';
|
||||
|
||||
export default Empty;
|
||||
12
src/components/Failure/index.tsx
Normal 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;
|
||||
48
src/components/Header/index.tsx
Normal 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;
|
||||
48
src/components/Header/style.less
Normal 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;
|
||||
}
|
||||
51
src/components/Info/index.tsx
Normal 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;
|
||||
39
src/components/Info/style.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/components/Label/index.tsx
Normal 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;
|
||||
24
src/components/Label/style.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
267
src/components/List/index.tsx
Normal 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);
|
||||
62
src/components/List/style.less
Normal 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;
|
||||
}
|
||||
16
src/components/PageLoading/index.tsx
Normal 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;
|
||||
30
src/components/SettingItem/index.tsx
Normal 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;
|
||||
34
src/components/SettingItem/style.less
Normal 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;
|
||||
}
|
||||
50
src/components/Tab/index.tsx
Normal 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;
|
||||
33
src/components/Tab/style.less
Normal 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
@@ -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%;
|
||||
}
|
||||
16
src/layouts/__tests__/index.test.tsx
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
1
src/pages/__tests__/__mocks__/umi-plugin-locale.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const formatMessage = (): string => 'Mock text';
|
||||
21
src/pages/__tests__/index.test.tsx
Normal 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']);
|
||||
});
|
||||
});
|
||||
77
src/pages/account/index.tsx
Normal 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);
|
||||
30
src/pages/account/style.less
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
36
src/pages/check/register.tsx
Normal 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;
|
||||
30
src/pages/check/service.ts
Normal 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;
|
||||
}
|
||||
49
src/pages/check/style.less
Normal 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
@@ -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
@@ -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;
|
||||
18
src/pages/estate/service.ts
Normal 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);
|
||||
}
|
||||
87
src/pages/estate/style.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/pages/home/_layout.tsx
Normal 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);
|
||||
103
src/pages/home/estate/index.tsx
Normal 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);
|
||||
110
src/pages/home/estate/model.ts
Normal 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;
|
||||
22
src/pages/home/estate/service.ts
Normal 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);
|
||||
}
|
||||
96
src/pages/home/estate/style.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/pages/home/fund/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const FundPage: React.FC = () => {
|
||||
return <div>Fund</div>
|
||||
}
|
||||
|
||||
export default FundPage;
|
||||
36
src/pages/home/home/index.tsx
Normal 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;
|
||||
17
src/pages/home/home/model.ts
Normal 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;
|
||||
57
src/pages/home/home/style.less
Normal 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;
|
||||
}
|
||||
17
src/pages/home/market/index.tsx
Normal 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;
|
||||
110
src/pages/home/market/model.ts
Normal 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;
|
||||
22
src/pages/home/market/service.ts
Normal 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);
|
||||
}
|
||||
8
src/pages/home/market/style.less
Normal file
@@ -0,0 +1,8 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
}
|
||||
107
src/pages/home/model.ts
Normal 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;
|
||||
73
src/pages/home/profile/index.tsx
Normal 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);
|
||||
60
src/pages/home/profile/style.less
Normal 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
@@ -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
@@ -0,0 +1,7 @@
|
||||
|
||||
.normal {
|
||||
font-family: Georgia, sans-serif;
|
||||
text-align: center;
|
||||
|
||||
line-height: 80vh;
|
||||
}
|
||||
13
src/pages/index.tsx
Executable 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
@@ -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);
|
||||
70
src/pages/system/index.tsx
Normal 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);
|
||||