feat: 实现交易和账户列表及详情页,并新增通用选择器组件和主标签导航。
This commit is contained in:
@@ -117,3 +117,5 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
||||
|
||||
278
package-lock.json
generated
278
package-lock.json
generated
@@ -13,11 +13,16 @@
|
||||
"@react-navigation/bottom-tabs": "^7.10.1",
|
||||
"@react-navigation/native": "^7.1.28",
|
||||
"@react-navigation/native-stack": "^7.11.0",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"react": "19.2.0",
|
||||
"react-native": "0.83.1",
|
||||
"react-native-gesture-handler": "^2.30.0",
|
||||
"react-native-gifted-charts": "^1.4.70",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-safe-area-context": "^5.5.2",
|
||||
"react-native-screens": "^4.20.0"
|
||||
"react-native-screens": "^4.20.0",
|
||||
"react-native-svg": "^15.15.1",
|
||||
"react-native-vector-icons": "^10.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
@@ -3603,12 +3608,30 @@
|
||||
"version": "19.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-native": {
|
||||
"version": "0.70.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz",
|
||||
"integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-native-vector-icons": {
|
||||
"version": "6.4.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz",
|
||||
"integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-native": "^0.70"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-test-renderer": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
|
||||
@@ -4550,6 +4573,12 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
@@ -5159,11 +5188,51 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.14",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
@@ -5396,6 +5465,61 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -5451,6 +5575,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
@@ -6707,6 +6843,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gifted-charts-core": {
|
||||
"version": "0.1.72",
|
||||
"resolved": "https://registry.npmjs.org/gifted-charts-core/-/gifted-charts-core-0.1.72.tgz",
|
||||
"integrity": "sha512-ID98gSvYA/6Egg/SxjjVAnFMpK0c4H9NZKkF4vRWtvJOioWegEGlQKN7BcQhuyKHr/HPmz7kGID3XJUuoM7UTQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-svg": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -8833,6 +8980,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -9394,6 +9547,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/nullthrows": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||
@@ -9416,7 +9581,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -9941,7 +10105,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
@@ -9953,7 +10116,6 @@
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
@@ -10203,6 +10365,40 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gifted-charts": {
|
||||
"version": "1.4.70",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gifted-charts/-/react-native-gifted-charts-1.4.70.tgz",
|
||||
"integrity": "sha512-nsbth3aMeMwu40ZNAn512TGZLks5Untplby5ABSe22+FLUdXYkdCzeR5/CodL6gxCcpsqQYXgAVvtujZnDRErA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"gifted-charts-core": "0.1.72"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo-linear-gradient": "*",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-linear-gradient": "*",
|
||||
"react-native-svg": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo-linear-gradient": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native-linear-gradient": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-linear-gradient": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz",
|
||||
"integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
|
||||
@@ -10227,6 +10423,76 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-svg": {
|
||||
"version": "15.15.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.1.tgz",
|
||||
"integrity": "sha512-ZUD1xwc3Hwo4cOmOLumjJVoc7lEf9oQFlHnLmgccLC19fNm6LVEdtB+Cnip6gEi0PG3wfvVzskViEtrySQP8Fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"css-tree": "^1.1.3",
|
||||
"warn-once": "0.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-vector-icons": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz",
|
||||
"integrity": "sha512-IFQ0RE57819hOUdFvgK4FowM5aMXg7C7XKsuGLevqXkkIJatc3QopN0wYrb2IrzUgmdpfP+QVIbI3S6h7M0btw==",
|
||||
"deprecated": "react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2",
|
||||
"yargs": "^16.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"fa-upgrade.sh": "bin/fa-upgrade.sh",
|
||||
"fa5-upgrade": "bin/fa5-upgrade.sh",
|
||||
"fa6-upgrade": "bin/fa6-upgrade.sh",
|
||||
"generate-icon": "bin/generate-icon.js"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-vector-icons/node_modules/cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-vector-icons/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-vector-icons/node_modules/yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
|
||||
@@ -15,11 +15,16 @@
|
||||
"@react-navigation/bottom-tabs": "^7.10.1",
|
||||
"@react-navigation/native": "^7.1.28",
|
||||
"@react-navigation/native-stack": "^7.11.0",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"react": "19.2.0",
|
||||
"react-native": "0.83.1",
|
||||
"react-native-gesture-handler": "^2.30.0",
|
||||
"react-native-gifted-charts": "^1.4.70",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
"react-native-safe-area-context": "^5.5.2",
|
||||
"react-native-screens": "^4.20.0"
|
||||
"react-native-screens": "^4.20.0",
|
||||
"react-native-svg": "^15.15.1",
|
||||
"react-native-vector-icons": "^10.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useTheme } from '../../contexts';
|
||||
import { accountService } from '../../services';
|
||||
import { spacing, borderRadius, typography } from '../../theme';
|
||||
import type { Account } from '../../types';
|
||||
import { AppIcon } from './AppIcon';
|
||||
|
||||
interface AccountSelectorProps {
|
||||
value?: number;
|
||||
@@ -33,12 +34,12 @@ interface AccountSelectorProps {
|
||||
|
||||
// 账户类型图标映射
|
||||
const ACCOUNT_ICONS: Record<string, string> = {
|
||||
cash: '💵',
|
||||
debit_card: '💳',
|
||||
credit_card: '💳',
|
||||
e_wallet: '📱',
|
||||
credit_line: '🏦',
|
||||
investment: '📈',
|
||||
cash: 'cash',
|
||||
debit_card: 'credit-card',
|
||||
credit_card: 'credit-card-multiple',
|
||||
e_wallet: 'cellphone-check',
|
||||
credit_line: 'bank',
|
||||
investment: 'chart-line',
|
||||
};
|
||||
|
||||
export function AccountSelector({
|
||||
@@ -119,7 +120,7 @@ export function AccountSelector({
|
||||
if (account.icon && !account.icon.includes(':')) {
|
||||
return account.icon;
|
||||
}
|
||||
return ACCOUNT_ICONS[account.type] || '💰';
|
||||
return ACCOUNT_ICONS[account.type] || 'wallet';
|
||||
};
|
||||
|
||||
const formatBalance = (balance: number) => {
|
||||
@@ -158,7 +159,11 @@ export function AccountSelector({
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.accountIcon}>
|
||||
<Text style={styles.accountIconText}>{getAccountIcon(item)}</Text>
|
||||
<AppIcon
|
||||
name={getAccountIcon(item)}
|
||||
size={22}
|
||||
color={colors.primary[500]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.accountInfo}>
|
||||
<Text style={styles.accountName}>{item.name}</Text>
|
||||
@@ -195,7 +200,12 @@ export function AccountSelector({
|
||||
>
|
||||
{selectedAccount ? (
|
||||
<View style={styles.selectedContent}>
|
||||
<Text style={styles.selectedIcon}>{getAccountIcon(selectedAccount)}</Text>
|
||||
<AppIcon
|
||||
name={getAccountIcon(selectedAccount)}
|
||||
size={24}
|
||||
color={colors.primary[500]}
|
||||
style={{ marginRight: spacing.md }}
|
||||
/>
|
||||
<View style={styles.selectedInfo}>
|
||||
<Text style={styles.selectedText}>{selectedAccount.name}</Text>
|
||||
<Text style={styles.selectedBalance}>{formatBalance(selectedAccount.balance)}</Text>
|
||||
|
||||
21
src/components/common/AppIcon.tsx
Normal file
21
src/components/common/AppIcon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Text, TextStyle } from 'react-native'; // Removed StyleProp
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
interface AppIconProps {
|
||||
name: string;
|
||||
size?: number;
|
||||
color?: string;
|
||||
style?: TextStyle; // Simplification
|
||||
}
|
||||
|
||||
export const AppIcon: React.FC<AppIconProps> = ({ name, size = 24, color, style }) => {
|
||||
// Check if likely emoji (non-ascii)
|
||||
const isEmoji = /[^\u0000-\u007F]/.test(name);
|
||||
|
||||
if (isEmoji) {
|
||||
return <Text style={[{ fontSize: size, color }, style]}>{name}</Text>;
|
||||
}
|
||||
|
||||
return <MaterialCommunityIcons name={name} size={size} color={color} style={style} />;
|
||||
};
|
||||
@@ -16,11 +16,12 @@ import { useTheme } from '../../contexts';
|
||||
import { categoryService } from '../../services';
|
||||
import { spacing, borderRadius, typography } from '../../theme';
|
||||
import type { Category, TransactionType } from '../../types';
|
||||
import { AppIcon } from './AppIcon';
|
||||
|
||||
interface CategorySelectorProps {
|
||||
value?: number;
|
||||
onChange?: (categoryId: number, category: Category) => void;
|
||||
type?: TransactionType; // Made optional as it might be inferred or not needed if categories passed
|
||||
type?: TransactionType;
|
||||
disabled?: boolean;
|
||||
|
||||
// Modal Mode props
|
||||
@@ -34,33 +35,33 @@ interface CategorySelectorProps {
|
||||
// 默认分类图标映射
|
||||
const CATEGORY_ICONS: Record<string, string> = {
|
||||
// 支出分类
|
||||
餐饮: '🍔',
|
||||
交通: '🚗',
|
||||
购物: '🛒',
|
||||
娱乐: '🎮',
|
||||
医疗: '💊',
|
||||
教育: '📚',
|
||||
居住: '🏠',
|
||||
通讯: '📱',
|
||||
服饰: '👔',
|
||||
美容: '💄',
|
||||
运动: '⚽',
|
||||
旅行: '✈️',
|
||||
宠物: '🐱',
|
||||
社交: '👥',
|
||||
礼物: '🎁',
|
||||
办公: '💼',
|
||||
数码: '💻',
|
||||
其他支出: '📦',
|
||||
餐饮: 'food',
|
||||
交通: 'car',
|
||||
购物: 'shopping',
|
||||
娱乐: 'gamepad-variant',
|
||||
医疗: 'pill',
|
||||
教育: 'school',
|
||||
居住: 'home',
|
||||
通讯: 'cellphone',
|
||||
服饰: 'tshirt-crew',
|
||||
美容: 'lipstick',
|
||||
运动: 'soccer',
|
||||
旅行: 'airplane',
|
||||
宠物: 'cat',
|
||||
社交: 'account-group',
|
||||
礼物: 'gift',
|
||||
办公: 'briefcase',
|
||||
数码: 'laptop',
|
||||
其他支出: 'package-variant',
|
||||
// 收入分类
|
||||
工资: '💰',
|
||||
奖金: '🎉',
|
||||
投资: '📈',
|
||||
兼职: '💼',
|
||||
理财: '🏦',
|
||||
红包: '🧧',
|
||||
报销: '📋',
|
||||
其他收入: '💵',
|
||||
工资: 'cash-multiple',
|
||||
奖金: 'party-popper',
|
||||
投资: 'chart-line',
|
||||
兼职: 'briefcase-clock',
|
||||
理财: 'bank',
|
||||
红包: 'email-heart',
|
||||
报销: 'clipboard-check',
|
||||
其他收入: 'cash-plus',
|
||||
};
|
||||
|
||||
export function CategorySelector({
|
||||
@@ -134,7 +135,7 @@ export function CategorySelector({
|
||||
return category.icon;
|
||||
}
|
||||
// 否则使用默认图标映射
|
||||
return CATEGORY_ICONS[category.name] || (type === 'expense' ? '💸' : '💰');
|
||||
return CATEGORY_ICONS[category.name] || (type === 'expense' ? 'cash-minus' : 'cash-plus');
|
||||
};
|
||||
|
||||
const styles = createStyles(colors, isDark);
|
||||
@@ -175,7 +176,11 @@ export function CategorySelector({
|
||||
currentId === item.id && styles.categoryIconSelected,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.categoryIconText}>{getCategoryIcon(item)}</Text>
|
||||
<AppIcon
|
||||
name={getCategoryIcon(item)}
|
||||
size={28}
|
||||
color={currentId === item.id ? '#FFFFFF' : colors.text}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
@@ -213,7 +218,12 @@ export function CategorySelector({
|
||||
>
|
||||
{selectedCategory ? (
|
||||
<View style={styles.selectedContent}>
|
||||
<Text style={styles.selectedIcon}>{getCategoryIcon(selectedCategory)}</Text>
|
||||
<AppIcon
|
||||
name={getCategoryIcon(selectedCategory)}
|
||||
size={20}
|
||||
color={colors.text}
|
||||
style={{ marginRight: spacing.sm }}
|
||||
/>
|
||||
<Text style={styles.selectedText}>{selectedCategory.name}</Text>
|
||||
</View>
|
||||
) : (
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../../contexts';
|
||||
import { spacing, borderRadius, typography } from '../../theme';
|
||||
import { AppIcon } from './AppIcon';
|
||||
|
||||
interface IconSelectorProps {
|
||||
visible: boolean;
|
||||
@@ -27,43 +28,43 @@ interface IconSelectorProps {
|
||||
const ICON_CATEGORIES = [
|
||||
{
|
||||
name: '通用',
|
||||
icons: ['💰', '💵', '💴', '💶', '💷', '💸', '🏦', '🏧', '💳', '💎'],
|
||||
icons: ['cash', 'cash-multiple', 'currency-usd', 'currency-eur', 'credit-card', 'bank', 'wallet', 'piggy-bank', 'chart-line', 'safe'],
|
||||
},
|
||||
{
|
||||
name: '餐饮',
|
||||
icons: ['🍔', '🍕', '🍜', '🍣', '🍱', '☕', '🍺', '🍰', '🍎', '🥗'],
|
||||
icons: ['food', 'food-outline', 'coffee', 'cup', 'glass-cocktail', 'cake', 'pizza', 'hamburger', 'noodles', 'fish'],
|
||||
},
|
||||
{
|
||||
name: '购物',
|
||||
icons: ['🛒', '🛍️', '👗', '👟', '💄', '🎁', '📱', '💻', '🎮', '📚'],
|
||||
icons: ['cart', 'shopping', 'tshirt-crew', 'shoe-sneaker', 'lipstick', 'gift', 'cellphone', 'laptop', 'gamepad-variant', 'book-open'],
|
||||
},
|
||||
{
|
||||
name: '交通',
|
||||
icons: ['🚗', '🚌', '🚇', '✈️', '🚂', '🚕', '⛽', '🚲', '🛵', '🚀'],
|
||||
icons: ['car', 'bus', 'train', 'airplane', 'taxi', 'gas-station', 'bike', 'moped', 'rocket', 'map-marker'],
|
||||
},
|
||||
{
|
||||
name: '居家',
|
||||
icons: ['🏠', '🛋️', '🛏️', '🚿', '💡', '🔧', '🧹', '🧺', '🪴', '🏡'],
|
||||
icons: ['home', 'sofa', 'bed', 'shower', 'lightbulb', 'wrench', 'broom', 'washing-machine', 'flower', 'home-account'],
|
||||
},
|
||||
{
|
||||
name: '娱乐',
|
||||
icons: ['🎬', '🎵', '🎮', '🎯', '🎨', '📷', '🎤', '🎭', '🎪', '🎢'],
|
||||
icons: ['movie', 'music', 'controller-classic', 'target', 'palette', 'camera', 'microphone', 'drama-masks', 'ticket', 'ferris-wheel'],
|
||||
},
|
||||
{
|
||||
name: '健康',
|
||||
icons: ['💊', '🏥', '🩺', '💉', '🧘', '🏃', '🚴', '⚽', '🏀', '🎾'],
|
||||
icons: ['pill', 'hospital-building', 'stethoscope', 'needle', 'yoga', 'run', 'bike-fast', 'soccer', 'basketball', 'tennis'],
|
||||
},
|
||||
{
|
||||
name: '教育',
|
||||
icons: ['📚', '🎓', '✏️', '📐', '🔬', '🧪', '📝', '💼', '🖥️', '📖'],
|
||||
icons: ['school', 'book-open-page-variant', 'pencil', 'ruler-square', 'microscope', 'flask', 'clipboard-text', 'briefcase', 'monitor', 'brain'],
|
||||
},
|
||||
{
|
||||
name: '社交',
|
||||
icons: ['🎂', '🎉', '💐', '🍾', '🎊', '💌', '👨👩👧', '👥', '🤝', '❤️'],
|
||||
icons: ['cake-variant', 'party-popper', 'flower-tulip', 'glass-wine', 'cards-playing', 'email-heart', 'account-group', 'account-multiple', 'handshake', 'heart'],
|
||||
},
|
||||
{
|
||||
name: '其他',
|
||||
icons: ['📦', '🔑', '🎫', '📬', '🗂️', '📎', '✂️', '🔒', '⭐', '❓'],
|
||||
icons: ['package-variant', 'key', 'ticket-confirmation', 'mailbox', 'folder', 'paperclip', 'scissors', 'lock', 'star', 'help-circle'],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -144,7 +145,11 @@ export default function IconSelector({
|
||||
]}
|
||||
onPress={() => handleSelect(item)}
|
||||
>
|
||||
<Text style={styles.iconEmoji}>{item}</Text>
|
||||
<AppIcon
|
||||
name={item}
|
||||
size={28}
|
||||
color={selectedIcon === item ? colors.primary[500] : colors.text}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
/>
|
||||
@@ -226,7 +231,4 @@ const createStyles = (colors: any, isDark: boolean) =>
|
||||
borderWidth: 2,
|
||||
borderColor: colors.primary[500],
|
||||
},
|
||||
iconEmoji: {
|
||||
fontSize: 28,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,3 +7,5 @@ export { AccountSelector } from './AccountSelector';
|
||||
export { default as DatePickerModal } from './DatePickerModal';
|
||||
export { default as AmountInput } from './AmountInput';
|
||||
export { default as IconSelector } from './IconSelector';
|
||||
export { AppIcon } from './AppIcon';
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useNavigation } from '@react-navigation/native';
|
||||
import type { MainTabParamList } from './types';
|
||||
import { useTheme } from '../contexts';
|
||||
import { spacing } from '../theme';
|
||||
import { AppIcon } from '../components/common/AppIcon';
|
||||
|
||||
// 页面组件
|
||||
import HomeScreen from '../screens/Home/HomeScreen';
|
||||
@@ -21,17 +22,21 @@ const Tab = createBottomTabNavigator<MainTabParamList>();
|
||||
// 简单的图标组件
|
||||
function TabIcon({ name, focused }: { name: string; focused: boolean }) {
|
||||
const iconMap: Record<string, string> = {
|
||||
Home: '🏠',
|
||||
Transactions: '📝',
|
||||
AddTransaction: '➕',
|
||||
Accounts: '💰',
|
||||
Settings: '⚙️',
|
||||
Home: focused ? 'home' : 'home-outline',
|
||||
Transactions: focused ? 'file-document' : 'file-document-outline',
|
||||
AddTransaction: 'plus',
|
||||
Accounts: focused ? 'wallet' : 'wallet-outline',
|
||||
Settings: focused ? 'cog' : 'cog-outline',
|
||||
};
|
||||
|
||||
const color = focused ? '#1a73e8' : '#666666'; // Will be overridden by tabBarActiveTintColor
|
||||
|
||||
return (
|
||||
<Text style={{ fontSize: focused ? 26 : 24, opacity: focused ? 1 : 0.7 }}>
|
||||
{iconMap[name] || '📱'}
|
||||
</Text>
|
||||
<AppIcon
|
||||
name={iconMap[name] || 'circle'}
|
||||
size={24}
|
||||
color={color} // Logic in Navigator handles color usually, but we pass it anyway or let parent handle style
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +55,7 @@ function AddButton() {
|
||||
style={[styles.addButton, { backgroundColor: colors.primary[500] }]}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={styles.addButtonText}>+</Text>
|
||||
<AppIcon name="plus" size={32} color="#FFFFFF" />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
@@ -76,8 +81,18 @@ export default function MainTabNavigator() {
|
||||
},
|
||||
tabBarActiveTintColor: colors.primary[500],
|
||||
tabBarInactiveTintColor: colors.textSecondary,
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabIcon name={route.name} focused={focused} />
|
||||
tabBarIcon: ({ focused, color }) => (
|
||||
<AppIcon
|
||||
name={
|
||||
route.name === 'Home' ? (focused ? 'home' : 'home-outline') :
|
||||
route.name === 'Transactions' ? (focused ? 'file-document' : 'file-document-outline') :
|
||||
route.name === 'Accounts' ? (focused ? 'wallet' : 'wallet-outline') :
|
||||
route.name === 'Settings' ? (focused ? 'cog' : 'cog-outline') :
|
||||
'circle'
|
||||
}
|
||||
size={24}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
tabBarLabelStyle: {
|
||||
fontSize: 11,
|
||||
@@ -131,10 +146,4 @@ const styles = StyleSheet.create({
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
},
|
||||
addButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 32,
|
||||
fontWeight: '300',
|
||||
marginTop: -2,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import { spacing, borderRadius, typography } from '../../theme';
|
||||
import { formatCurrency, formatDate, getAccountTypeLabel } from '../../utils/format';
|
||||
import type { Account, Transaction } from '../../types';
|
||||
import type { RootStackParamList } from '../../navigation/types';
|
||||
import { AppIcon } from '../../components/common/AppIcon';
|
||||
|
||||
type RouteProps = RouteProp<RootStackParamList, 'AccountDetail'>;
|
||||
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
@@ -118,7 +119,11 @@ export default function AccountDetailScreen() {
|
||||
onPress={() => navigation.navigate('TransactionDetail', { transactionId: item.id })}
|
||||
>
|
||||
<View style={styles.transactionIcon}>
|
||||
<Text style={styles.transactionEmoji}>{item.categoryIcon || '📦'}</Text>
|
||||
<AppIcon
|
||||
name={item.categoryIcon || 'package-variant'}
|
||||
size={20}
|
||||
color={colors.primary[500]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.transactionInfo}>
|
||||
<Text style={styles.transactionCategory}>{item.categoryName}</Text>
|
||||
@@ -158,7 +163,7 @@ export default function AccountDetailScreen() {
|
||||
style={styles.backButton}
|
||||
onPress={() => navigation.goBack()}
|
||||
>
|
||||
<Text style={styles.backIcon}>←</Text>
|
||||
<AppIcon name="arrow-left" size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>账户详情</Text>
|
||||
<TouchableOpacity style={styles.headerButton} onPress={handleEdit}>
|
||||
@@ -180,7 +185,11 @@ export default function AccountDetailScreen() {
|
||||
{/* 账户卡片 */}
|
||||
<View style={styles.accountCard}>
|
||||
<View style={styles.accountIconContainer}>
|
||||
<Text style={styles.accountIcon}>{account.icon || '💳'}</Text>
|
||||
<AppIcon
|
||||
name={account.icon || 'credit-card'}
|
||||
size={36}
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.accountName}>{account.name}</Text>
|
||||
<Text style={styles.accountType}>{getAccountTypeLabel(account.type)}</Text>
|
||||
@@ -229,7 +238,7 @@ export default function AccountDetailScreen() {
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.emptyTransactions}>
|
||||
<Text style={styles.emptyIcon}>📝</Text>
|
||||
<AppIcon name="clipboard-text-outline" size={40} color={colors.textTertiary} style={{ marginBottom: spacing.md }} />
|
||||
<Text style={styles.emptyTransactionsText}>暂无交易记录</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { accountService } from '../../services';
|
||||
import { spacing, borderRadius, typography } from '../../theme';
|
||||
import type { Account, AssetOverview } from '../../types';
|
||||
import type { RootStackParamList } from '../../navigation/types';
|
||||
import { AppIcon } from '../../components/common/AppIcon';
|
||||
|
||||
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
||||
@@ -79,14 +80,14 @@ export default function AccountsScreen() {
|
||||
|
||||
const getAccountIcon = (type: string) => {
|
||||
const icons: Record<string, string> = {
|
||||
cash: '💵',
|
||||
debit_card: '💳',
|
||||
credit_card: '💳',
|
||||
e_wallet: '📱',
|
||||
credit_line: '🏦',
|
||||
investment: '📈',
|
||||
cash: 'cash',
|
||||
debit_card: 'credit-card',
|
||||
credit_card: 'credit-card-multiple',
|
||||
e_wallet: 'cellphone-check',
|
||||
credit_line: 'bank',
|
||||
investment: 'chart-line',
|
||||
};
|
||||
return icons[type] || '💰';
|
||||
return icons[type] || 'wallet';
|
||||
};
|
||||
|
||||
// 分组账户
|
||||
@@ -105,13 +106,15 @@ export default function AccountsScreen() {
|
||||
style={styles.transferButton}
|
||||
onPress={() => navigation.navigate('Transfer')}
|
||||
>
|
||||
<Text style={styles.transferButtonText}>🔄 转账</Text>
|
||||
<AppIcon name="swap-horizontal" size={16} color={colors.semantic.transfer} style={{ marginRight: 4 }} />
|
||||
<Text style={styles.transferButtonText}>转账</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.addButton}
|
||||
onPress={() => navigation.navigate('AddAccount')}
|
||||
>
|
||||
<Text style={styles.addButtonText}>+ 添加</Text>
|
||||
<AppIcon name="plus" size={16} color="#FFFFFF" style={{ marginRight: 4 }} />
|
||||
<Text style={styles.addButtonText}>添加</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -163,7 +166,11 @@ export default function AccountsScreen() {
|
||||
onPress={() => navigation.navigate('AccountDetail', { accountId: account.id })}
|
||||
>
|
||||
<View style={styles.accountIcon}>
|
||||
<Text style={styles.accountIconText}>{getAccountIcon(account.type)}</Text>
|
||||
<AppIcon
|
||||
name={getAccountIcon(account.type)}
|
||||
size={22}
|
||||
color={colors.primary[500]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.accountInfo}>
|
||||
<Text style={styles.accountName}>{account.name}</Text>
|
||||
@@ -191,7 +198,11 @@ export default function AccountsScreen() {
|
||||
onPress={() => navigation.navigate('AccountDetail', { accountId: account.id })}
|
||||
>
|
||||
<View style={styles.accountIcon}>
|
||||
<Text style={styles.accountIconText}>{getAccountIcon(account.type)}</Text>
|
||||
<AppIcon
|
||||
name={getAccountIcon(account.type)}
|
||||
size={22}
|
||||
color={colors.primary[500]}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.accountInfo}>
|
||||
<Text style={styles.accountName}>{account.name}</Text>
|
||||
@@ -209,7 +220,7 @@ export default function AccountsScreen() {
|
||||
{/* 空状态 */}
|
||||
{accounts.length === 0 && (
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={styles.emptyIcon}>💳</Text>
|
||||
<AppIcon name="credit-card-off-outline" size={48} color={colors.textTertiary} style={{ marginBottom: 16 }} />
|
||||
<Text style={styles.emptyText}>暂无账户</Text>
|
||||
<Text style={styles.emptySubText}>点击右上角添加账户</Text>
|
||||
</View>
|
||||
@@ -219,6 +230,7 @@ export default function AccountsScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const createStyles = (colors: any, isDark: boolean, insets: any) =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
|
||||
@@ -21,14 +21,15 @@ import { spacing, borderRadius, typography } from '../../theme';
|
||||
import { formatCurrency, formatDate, formatRelativeTime } from '../../utils/format';
|
||||
import type { Transaction } from '../../types';
|
||||
import type { RootStackParamList } from '../../navigation/types';
|
||||
import { AppIcon } from '../../components/common/AppIcon';
|
||||
|
||||
type RouteProps = RouteProp<RootStackParamList, 'TransactionDetail'>;
|
||||
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
||||
const TYPE_CONFIG = {
|
||||
income: { label: '收入', icon: '💰', colorKey: 'income' as const },
|
||||
expense: { label: '支出', icon: '💸', colorKey: 'expense' as const },
|
||||
transfer: { label: '转账', icon: '🔄', colorKey: 'transfer' as const },
|
||||
income: { label: '收入', icon: 'cash-plus', colorKey: 'income' as const },
|
||||
expense: { label: '支出', icon: 'cash-minus', colorKey: 'expense' as const },
|
||||
transfer: { label: '转账', icon: 'swap-horizontal', colorKey: 'transfer' as const },
|
||||
};
|
||||
|
||||
export default function TransactionDetailScreen() {
|
||||
@@ -114,7 +115,7 @@ export default function TransactionDetailScreen() {
|
||||
style={styles.backButton}
|
||||
onPress={() => navigation.goBack()}
|
||||
>
|
||||
<Text style={styles.backIcon}>←</Text>
|
||||
<AppIcon name="arrow-left" size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>交易详情</Text>
|
||||
<View style={styles.headerActions}>
|
||||
@@ -131,7 +132,7 @@ export default function TransactionDetailScreen() {
|
||||
{/* 金额卡片 */}
|
||||
<View style={styles.amountCard}>
|
||||
<View style={[styles.typeIcon, { backgroundColor: amountColor + '20' }]}>
|
||||
<Text style={styles.typeEmoji}>{typeConfig.icon}</Text>
|
||||
<AppIcon name={typeConfig.icon} size={32} color={amountColor} />
|
||||
</View>
|
||||
<Text style={[styles.amount, { color: amountColor }]}>
|
||||
{transaction.type === 'income' ? '+' : '-'}
|
||||
@@ -146,9 +147,12 @@ export default function TransactionDetailScreen() {
|
||||
label="分类"
|
||||
value={
|
||||
<View style={styles.categoryValue}>
|
||||
<Text style={styles.categoryIcon}>
|
||||
{transaction.categoryIcon || '📦'}
|
||||
</Text>
|
||||
<AppIcon
|
||||
name={transaction.categoryIcon || 'package-variant'}
|
||||
size={20}
|
||||
color={colors.text}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.categoryName}>
|
||||
{transaction.categoryName}
|
||||
</Text>
|
||||
@@ -161,9 +165,12 @@ export default function TransactionDetailScreen() {
|
||||
label="账户"
|
||||
value={
|
||||
<View style={styles.categoryValue}>
|
||||
<Text style={styles.categoryIcon}>
|
||||
{transaction.accountIcon || '💳'}
|
||||
</Text>
|
||||
<AppIcon
|
||||
name={transaction.accountIcon || 'credit-card'}
|
||||
size={20}
|
||||
color={colors.text}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.categoryName}>
|
||||
{transaction.accountName}
|
||||
</Text>
|
||||
@@ -177,9 +184,12 @@ export default function TransactionDetailScreen() {
|
||||
label="转入账户"
|
||||
value={
|
||||
<View style={styles.categoryValue}>
|
||||
<Text style={styles.categoryIcon}>
|
||||
{transaction.toAccountIcon || '💳'}
|
||||
</Text>
|
||||
<AppIcon
|
||||
name={transaction.toAccountIcon || 'credit-card'}
|
||||
size={20}
|
||||
color={colors.text}
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text style={styles.categoryName}>
|
||||
{transaction.toAccountName}
|
||||
</Text>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { transactionService } from '../../services';
|
||||
import { spacing, borderRadius, typography } from '../../theme';
|
||||
import type { Transaction } from '../../types';
|
||||
import type { RootStackParamList } from '../../navigation/types';
|
||||
import { AppIcon } from '../../components/common/AppIcon';
|
||||
|
||||
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
||||
|
||||
@@ -105,9 +106,11 @@ export default function TransactionsScreen() {
|
||||
onPress={() => navigation.navigate('TransactionDetail', { transactionId: item.id })}
|
||||
>
|
||||
<View style={[styles.iconContainer, { backgroundColor: getTypeColor(item.type) + '20' }]}>
|
||||
<Text style={styles.iconText}>
|
||||
{item.categoryIcon || (item.type === 'income' ? '💰' : item.type === 'expense' ? '💸' : '🔄')}
|
||||
</Text>
|
||||
<AppIcon
|
||||
name={item.categoryIcon || (item.type === 'income' ? 'cash-plus' : item.type === 'expense' ? 'cash-minus' : 'swap-horizontal')}
|
||||
size={22}
|
||||
color={getTypeColor(item.type)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.transactionInfo}>
|
||||
<Text style={styles.transactionNote} numberOfLines={1}>
|
||||
@@ -155,7 +158,7 @@ export default function TransactionsScreen() {
|
||||
onEndReachedThreshold={0.3}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyState}>
|
||||
<Text style={styles.emptyIcon}>📝</Text>
|
||||
<AppIcon name="clipboard-text-outline" size={48} color={colors.textTertiary} style={{ marginBottom: 16 }} />
|
||||
<Text style={styles.emptyText}>暂无交易记录</Text>
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -98,11 +98,11 @@ export function getTransactionTypeColor(type: string, colors: any): string {
|
||||
export function getTransactionTypeIcon(type: string): string {
|
||||
switch (type) {
|
||||
case 'income':
|
||||
return '💰';
|
||||
return 'cash-plus';
|
||||
case 'expense':
|
||||
return '💸';
|
||||
return 'cash-minus';
|
||||
default:
|
||||
return '🔄';
|
||||
return 'swap-horizontal';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +126,12 @@ export function getAccountTypeLabel(type: string): string {
|
||||
*/
|
||||
export function getAccountTypeIcon(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
cash: '💵',
|
||||
debit_card: '💳',
|
||||
credit_card: '💳',
|
||||
e_wallet: '📱',
|
||||
credit_line: '🏦',
|
||||
investment: '📈',
|
||||
cash: 'cash',
|
||||
debit_card: 'credit-card',
|
||||
credit_card: 'credit-card-multiple',
|
||||
e_wallet: 'cellphone-check',
|
||||
credit_line: 'bank',
|
||||
investment: 'chart-line',
|
||||
};
|
||||
return icons[type] || '💰';
|
||||
return icons[type] || 'wallet';
|
||||
}
|
||||
|
||||
44
task.md
44
task.md
@@ -87,7 +87,7 @@
|
||||
- [x] 6.4.1 DatePickerModal - 日期选择组件
|
||||
- [x] 6.4.2 AmountInput - 金额输入组件
|
||||
- [x] 6.4.3 IconSelector - 图标选择组件
|
||||
- [ ] 6.4.4 react-native-vector-icons 集成
|
||||
- [x] 6.4.4 react-native-vector-icons 集成
|
||||
|
||||
---
|
||||
|
||||
@@ -100,13 +100,43 @@
|
||||
|
||||
---
|
||||
|
||||
## ⏳ Phase 7: UI 优化 (待开始)
|
||||
## 🚀 Phase 9: 图表与可视化 (Reports 2.0)
|
||||
>
|
||||
> 目标: 集成专业图表库,实现可视化报表
|
||||
|
||||
- [ ] 7.1 主页最近交易列表
|
||||
- [ ] 7.2 骨架屏加载效果
|
||||
- [ ] 7.3 下拉刷新动画
|
||||
- [ ] 7.4 空状态优化
|
||||
- [ ] 7.5 错误处理和提示
|
||||
- [ ] 9.1 集成图表库 (e.g. react-native-gifted-charts or victory-native)
|
||||
- [ ] 9.2 收支趋势折线图 (Trend Line Chart)
|
||||
- [ ] 9.3 支出分类饼图 (Category Pie Chart)
|
||||
- [ ] 9.4 月度收支对比柱状图 (Bar Chart)
|
||||
|
||||
## 🔄 Phase 10: 核心功能补全
|
||||
>
|
||||
> 目标: 补齐 Web 端定义的 P1 核心记账能力
|
||||
|
||||
- [ ] 10.1 周期性交易模块 (Recurring Transactions)
|
||||
- [ ] 10.1.1 周期交易服务 (recurringTransactionService)
|
||||
- [ ] 10.1.2 周期交易列表页
|
||||
- [ ] 10.1.3 创建/编辑周期交易
|
||||
- [ ] 10.2 多账本系统 (Ledger System)
|
||||
- [ ] 10.2.1 账本服务 (ledgerService)
|
||||
- [ ] 10.2.2 账本管理页 (新建/切换/编辑)
|
||||
|
||||
## 🐷 Phase 11: 财务目标 (Savings & Goals)
|
||||
>
|
||||
> 目标: 增强预算模块,增加存钱目标
|
||||
|
||||
- [ ] 11.1 存钱罐服务 (piggyBankService)
|
||||
- [ ] 11.2 存钱罐列表组件 (BudgetScreen 集成)
|
||||
- [ ] 11.3 存入/取出操作逻辑
|
||||
|
||||
## 🛠️ Phase 12: 工具与生态
|
||||
>
|
||||
> 目标: 完善 P2 功能,对齐 Web 端体验
|
||||
|
||||
- [ ] 12.1 交易日历视图 (Calendar View)
|
||||
- [ ] 12.2 数据导出功能 (Export to CSV/Excel)
|
||||
- [ ] 12.3 汇率换算工具 (Exchange Rate)
|
||||
- [ ] 12.4 消息通知中心 (Notifications)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user