devui开发
This commit is contained in:
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#2E2B43",
|
||||
"titleBar.activeBackground": "#403C5E",
|
||||
"titleBar.activeForeground": "#FCFBFD"
|
||||
}
|
||||
}
|
||||
@@ -85,12 +85,16 @@
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
"src/styles.css",
|
||||
"node_modules/ng-devui/devui.min.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": "e908ce05-abf1-423a-9209-a4ceaf605dc9"
|
||||
}
|
||||
}
|
||||
|
||||
378
package-lock.json
generated
378
package-lock.json
generated
@@ -16,6 +16,8 @@
|
||||
"@angular/platform-browser": "^18.2.0",
|
||||
"@angular/platform-browser-dynamic": "^18.2.0",
|
||||
"@angular/router": "^18.2.0",
|
||||
"@devui-design/icons": "^1.4.0",
|
||||
"ng-devui": "^18.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.10"
|
||||
@@ -460,6 +462,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@angular/cdk": {
|
||||
"version": "18.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz",
|
||||
"integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"parse5": "^7.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^18.0.0 || ^19.0.0",
|
||||
"@angular/core": "^18.0.0 || ^19.0.0",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli": {
|
||||
"version": "18.2.21",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.21.tgz",
|
||||
@@ -607,6 +626,36 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler-cli/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler-cli/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "18.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.14.tgz",
|
||||
@@ -2354,7 +2403,6 @@
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
@@ -2438,6 +2486,12 @@
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/@devui-design/icons": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@devui-design/icons/-/icons-1.4.0.tgz",
|
||||
"integrity": "sha512-taAX1RNW0QHUqQTRPqLTYTB2PZIqUplhWeF4hcmWkSTjpWlDNI40DssG/WRb3sISkfBk/4BMUxxC5XeTL3jo7A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@discoveryjs/json-ext": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz",
|
||||
@@ -3907,6 +3961,16 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
|
||||
@@ -5113,7 +5177,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
@@ -5540,19 +5603,41 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
@@ -6132,6 +6217,16 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-vars-ponyfill": {
|
||||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.9.tgz",
|
||||
"integrity": "sha512-aZyLue5bdiGVNCiCclNjo123D8I7kyoYNUaAvz+H1JalX1ye4Ilz7jNRRH5YbM+dYD6ucejiydGwk7lol/GCXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.2",
|
||||
"get-css-data": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||
@@ -6165,6 +6260,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/date-format": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
|
||||
@@ -7298,6 +7409,12 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-css-data": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/get-css-data/-/get-css-data-2.1.1.tgz",
|
||||
"integrity": "sha512-JpMa/f5P4mDXKg6l5/2cHL5xNY77Jap7tHyduMa6BF0E2a7bQ6Tvaz2BIMjeVYZYLcmOZ5w2Ro0yVJEI41tMbw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||
@@ -8590,31 +8707,6 @@
|
||||
"source-map-support": "^0.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
@@ -8634,19 +8726,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/karma/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -8657,32 +8736,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/karma/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -9074,6 +9127,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
@@ -9846,6 +9905,26 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ng-devui": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ng-devui/-/ng-devui-18.0.0.tgz",
|
||||
"integrity": "sha512-QZzDqZ2cUF8w5VF65QASLIeE/2vpDr4iw9FCIbbFaQc4eLMb4lHRwRQFPyVatCpxe05k8qT2larICJbfJA6NJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^18.0.0",
|
||||
"@popperjs/core": "^2.5.4",
|
||||
"css-vars-ponyfill": "^2.3.2",
|
||||
"date-fns": "^2.20.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "^18.0.0",
|
||||
"@angular/common": "^18.0.0",
|
||||
"@angular/core": "^18.0.0",
|
||||
"@angular/forms": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nice-napi": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||
@@ -10542,7 +10621,7 @@
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
@@ -10583,7 +10662,7 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
@@ -11068,17 +11147,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
@@ -11112,7 +11203,6 @@
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regex-parser": {
|
||||
@@ -11506,70 +11596,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
|
||||
@@ -13767,44 +13793,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
|
||||
@@ -13830,32 +13818,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"@angular/platform-browser": "^18.2.0",
|
||||
"@angular/platform-browser-dynamic": "^18.2.0",
|
||||
"@angular/router": "^18.2.0",
|
||||
"@devui-design/icons": "^1.4.0",
|
||||
"ng-devui": "^18.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.10"
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/* 移除原来的样式,因为已经移到HTML文件中 */
|
||||
|
||||
/* 为首页内容添加样式 */
|
||||
.home-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 0.8rem;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.welcome-section h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
margin-top: 0;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.feature-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.feature-link:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-stats {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.home-content {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,3 @@
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * -->
|
||||
<!-- * * * * * * * to get started with your project! * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
<style>
|
||||
:host {
|
||||
--bright-blue: oklch(51.01% 0.274 263.83);
|
||||
@@ -45,7 +36,7 @@
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.125rem;
|
||||
font-size: 2rem;
|
||||
color: var(--gray-900);
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
@@ -61,276 +52,461 @@
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
/* 新增管理后台样式 */
|
||||
.app-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #dee2e6;
|
||||
padding: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 0;
|
||||
padding: 1rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
margin-left: 250px;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
.main-content.expanded {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
box-sizing: inherit;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.angular-logo {
|
||||
max-width: 9.2rem;
|
||||
.menu-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
margin-bottom: 3rem;
|
||||
.menu-toggle:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
margin-top: 1.75rem;
|
||||
.sidebar-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-top: 1.5rem;
|
||||
.sidebar-menu li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
background: var(--red-to-pink-to-purple-vertical-gradient);
|
||||
margin-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.pill-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.pill {
|
||||
.sidebar-menu a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--pill-accent: var(--bright-blue);
|
||||
background: color-mix(in srgb, var(--pill-accent) 5%, transparent);
|
||||
color: var(--pill-accent);
|
||||
padding-inline: 0.75rem;
|
||||
padding-block: 0.375rem;
|
||||
border-radius: 2.75rem;
|
||||
border: 0;
|
||||
transition: background 0.3s ease;
|
||||
font-family: var(--inter-font);
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.4rem;
|
||||
letter-spacing: -0.00875rem;
|
||||
padding: 0.75rem;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pill:hover {
|
||||
background: color-mix(in srgb, var(--pill-accent) 15%, transparent);
|
||||
.sidebar-menu a:hover,
|
||||
.sidebar-menu a.active {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pill-group .pill:nth-child(6n + 1) {
|
||||
--pill-accent: var(--bright-blue);
|
||||
}
|
||||
.pill-group .pill:nth-child(6n + 2) {
|
||||
--pill-accent: var(--french-violet);
|
||||
}
|
||||
.pill-group .pill:nth-child(6n + 3),
|
||||
.pill-group .pill:nth-child(6n + 4),
|
||||
.pill-group .pill:nth-child(6n + 5) {
|
||||
--pill-accent: var(--hot-red);
|
||||
.menu-item-text {
|
||||
margin-left: 0.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.pill-group svg {
|
||||
margin-inline-start: 0.25rem;
|
||||
.menu-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.73rem;
|
||||
margin-top: 1.5rem;
|
||||
.overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.social-links path {
|
||||
transition: fill 0.3s ease;
|
||||
fill: var(--gray-400);
|
||||
.overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.social-links a:hover svg path {
|
||||
fill: var(--gray-900);
|
||||
/* 响应式设计 - 平板 */
|
||||
@media (max-width: 1024px) {
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
.content {
|
||||
/* 响应式设计 - 手机 */
|
||||
@media (max-width: 768px) {
|
||||
.app-container {
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
background: var(--red-to-pink-to-purple-horizontal-gradient);
|
||||
margin-block: 1.5rem;
|
||||
height: auto;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
height: 60px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .sidebar-menu {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
margin: 0;
|
||||
padding: 0.25rem;
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.sidebar-menu a {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .sidebar-menu a {
|
||||
padding: 0.75rem 0.25rem;
|
||||
}
|
||||
|
||||
.menu-item-text {
|
||||
margin-left: 0;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏幕手机优化 */
|
||||
@media (max-width: 480px) {
|
||||
.sidebar-menu {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.sidebar-menu a {
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
|
||||
.menu-item-text {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.top-bar h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
font-size: 1.25rem;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超小屏幕优化 */
|
||||
@media (max-width: 360px) {
|
||||
.sidebar-menu li {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.menu-item-text {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.top-bar h1 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 首页内容样式 */
|
||||
.home-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 0.8rem;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.welcome-section h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
margin-top: 0;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.feature-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.feature-link:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-stats {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.home-content {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.dashboard-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class="main">
|
||||
<div class="content">
|
||||
<div class="left-side">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 982 239"
|
||||
fill="none"
|
||||
class="angular-logo"
|
||||
>
|
||||
<g clip-path="url(#a)">
|
||||
<path
|
||||
fill="url(#b)"
|
||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#c)"
|
||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="c"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FF41F8" />
|
||||
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5" />
|
||||
<stop offset="1" stop-color="#FF41F8" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="0"
|
||||
x2="982"
|
||||
y1="192"
|
||||
y2="192"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#F0060B" />
|
||||
<stop offset="0" stop-color="#F0070C" />
|
||||
<stop offset=".526" stop-color="#CC26D5" />
|
||||
<stop offset="1" stop-color="#7702FF" />
|
||||
</linearGradient>
|
||||
<clipPath id="a"><path fill="#fff" d="M0 0h982v239H0z" /></clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<h1>Hello, {{ title }}</h1>
|
||||
<p>Congratulations! Your app is running. 🎉</p>
|
||||
<!-- 主内容区域 -->
|
||||
<div class="app-container">
|
||||
<!-- 侧边栏 -->
|
||||
<nav class="sidebar" id="sidebar">
|
||||
<ul class="sidebar-menu">
|
||||
<li>
|
||||
<a routerLink="/" routerLinkActive="active">
|
||||
<span class="menu-icon">🏠</span>
|
||||
<span class="menu-item-text">首页</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a routerLink="/new-page" routerLinkActive="active">
|
||||
<span class="menu-icon">📄</span>
|
||||
<span class="menu-item-text">新页面</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a routerLink="/devui-form" routerLinkActive="active">
|
||||
<span class="menu-icon">📋</span>
|
||||
<span class="menu-item-text">DevUI表单</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 遮罩层,用于小屏幕下点击关闭菜单 -->
|
||||
<div class="overlay" id="overlay" (click)="toggleSidebar()"></div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content" id="mainContent">
|
||||
<!-- 顶部导航栏 -->
|
||||
<div class="top-bar">
|
||||
<button class="menu-toggle" (click)="toggleSidebar()">
|
||||
☰
|
||||
</button>
|
||||
<h1>管理系统</h1>
|
||||
</div>
|
||||
<div class="divider" role="separator" aria-label="Divider"></div>
|
||||
<div class="right-side">
|
||||
<div class="pill-group">
|
||||
@for (item of [
|
||||
{ title: 'Explore the Docs', link: 'https://angular.dev' },
|
||||
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
|
||||
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
|
||||
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
|
||||
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
|
||||
]; track item.title) {
|
||||
<a
|
||||
class="pill"
|
||||
[href]="item.link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="14"
|
||||
viewBox="0 -960 960 960"
|
||||
width="14"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
|
||||
<!-- 路由出口 -->
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<!-- 默认首页内容 -->
|
||||
<div class="home-content" *ngIf="isHomePage()">
|
||||
<div class="dashboard-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">用户总数</div>
|
||||
<div class="stat-value">12,345</div>
|
||||
<div class="stat-change">↑ 12% 上月</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">订单数量</div>
|
||||
<div class="stat-value">1,234</div>
|
||||
<div class="stat-change">↑ 5% 上周</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">收入</div>
|
||||
<div class="stat-value">¥56,789</div>
|
||||
<div class="stat-change">↑ 8% 今天</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">任务完成率</div>
|
||||
<div class="stat-value">89%</div>
|
||||
<div class="stat-change">↑ 3% 本月</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a
|
||||
href="https://github.com/angular/angular"
|
||||
aria-label="Github"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Github"
|
||||
>
|
||||
<path
|
||||
d="M12.3047 0C5.50634 0 0 5.50942 0 12.3047C0 17.7423 3.52529 22.3535 8.41332 23.9787C9.02856 24.0946 9.25414 23.7142 9.25414 23.3871C9.25414 23.0949 9.24389 22.3207 9.23876 21.2953C5.81601 22.0377 5.09414 19.6444 5.09414 19.6444C4.53427 18.2243 3.72524 17.8449 3.72524 17.8449C2.61064 17.082 3.81137 17.0973 3.81137 17.0973C5.04697 17.1835 5.69604 18.3647 5.69604 18.3647C6.79321 20.2463 8.57636 19.7029 9.27978 19.3881C9.39052 18.5924 9.70736 18.0499 10.0591 17.7423C7.32641 17.4347 4.45429 16.3765 4.45429 11.6618C4.45429 10.3185 4.9311 9.22133 5.72065 8.36C5.58222 8.04931 5.16694 6.79833 5.82831 5.10337C5.82831 5.10337 6.85883 4.77319 9.2121 6.36459C10.1965 6.09082 11.2424 5.95546 12.2883 5.94931C13.3342 5.95546 14.3801 6.09082 15.3644 6.36459C17.7023 4.77319 18.7328 5.10337 18.7328 5.10337C19.3942 6.79833 18.9789 8.04931 18.8559 8.36C19.6403 9.22133 20.1171 10.3185 20.1171 11.6618C20.1171 16.3888 17.2409 17.4296 14.5031 17.7321C14.9338 18.1012 15.3337 18.8559 15.3337 20.0084C15.3337 21.6552 15.3183 22.978 15.3183 23.3779C15.3183 23.7009 15.5336 24.0854 16.1642 23.9623C21.0871 22.3484 24.6094 17.7341 24.6094 12.3047C24.6094 5.50942 19.0999 0 12.3047 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/angular"
|
||||
aria-label="Twitter"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Twitter"
|
||||
>
|
||||
<path
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/channel/UCbn1OgGei-DV7aSRo_HaAiw"
|
||||
aria-label="Youtube"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="29"
|
||||
height="20"
|
||||
viewBox="0 0 29 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Youtube"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M27.4896 1.52422C27.9301 1.96749 28.2463 2.51866 28.4068 3.12258C29.0004 5.35161 29.0004 10 29.0004 10C29.0004 10 29.0004 14.6484 28.4068 16.8774C28.2463 17.4813 27.9301 18.0325 27.4896 18.4758C27.0492 18.9191 26.5 19.2389 25.8972 19.4032C23.6778 20 14.8068 20 14.8068 20C14.8068 20 5.93586 20 3.71651 19.4032C3.11363 19.2389 2.56449 18.9191 2.12405 18.4758C1.68361 18.0325 1.36732 17.4813 1.20683 16.8774C0.613281 14.6484 0.613281 10 0.613281 10C0.613281 10 0.613281 5.35161 1.20683 3.12258C1.36732 2.51866 1.68361 1.96749 2.12405 1.52422C2.56449 1.08095 3.11363 0.76113 3.71651 0.596774C5.93586 0 14.8068 0 14.8068 0C14.8068 0 23.6778 0 25.8972 0.596774C26.5 0.76113 27.0492 1.08095 27.4896 1.52422ZM19.3229 10L11.9036 5.77905V14.221L19.3229 10Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div class="welcome-section">
|
||||
<h2>欢迎使用管理系统</h2>
|
||||
<p>这是一个功能强大的管理平台,可以帮助您高效地管理业务和数据。</p>
|
||||
<p>通过左侧导航菜单可以访问系统的各个功能模块。</p>
|
||||
</div>
|
||||
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<h3>📊 数据统计</h3>
|
||||
<p>实时监控关键业务指标,可视化展示数据趋势</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>👥 用户管理</h3>
|
||||
<p>轻松管理用户信息、权限分配和角色设置</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>⚙️ 系统设置</h3>
|
||||
<p>灵活配置系统参数,满足个性化需求</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📋 DevUI 表单</h3>
|
||||
<p>基于 DevUI 设计的高级表单示例,展示表单的各种用法和技巧</p>
|
||||
<a routerLink="/devui-form" class="feature-link">查看示例</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
|
||||
<router-outlet />
|
||||
</div>
|
||||
@@ -1,13 +1,113 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { Component, Renderer2, OnInit, OnDestroy } from '@angular/core';
|
||||
import { RouterOutlet, RouterLink, RouterLinkActive, Router } from '@angular/router';
|
||||
import { NgIf } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [RouterOutlet],
|
||||
imports: [RouterOutlet, RouterLink, RouterLinkActive, NgIf],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css'
|
||||
})
|
||||
export class AppComponent {
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
title = 'xieu';
|
||||
}
|
||||
|
||||
private clickListener!: () => void;
|
||||
private resizeListener!: () => void;
|
||||
|
||||
constructor(private renderer: Renderer2, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
// 监听文档点击事件,用于在小屏幕上点击外部区域关闭菜单
|
||||
this.clickListener = this.renderer.listen('document', 'click', (event) => {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const toggleButton = document.querySelector('.menu-toggle');
|
||||
const overlay = document.getElementById('overlay');
|
||||
|
||||
if (sidebar && toggleButton &&
|
||||
!sidebar.contains(event.target) &&
|
||||
!toggleButton.contains(event.target) &&
|
||||
!sidebar.classList.contains('collapsed') &&
|
||||
window.innerWidth <= 768) {
|
||||
this.toggleSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听窗口大小变化
|
||||
this.resizeListener = this.renderer.listen('window', 'resize', () => {
|
||||
this.onResize();
|
||||
});
|
||||
|
||||
// 初始化时检查窗口大小
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// 清理事件监听器
|
||||
if (this.clickListener) {
|
||||
this.clickListener();
|
||||
}
|
||||
if (this.resizeListener) {
|
||||
this.resizeListener();
|
||||
}
|
||||
}
|
||||
|
||||
toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const mainContent = document.getElementById('mainContent');
|
||||
const overlay = document.getElementById('overlay');
|
||||
|
||||
if (sidebar && mainContent) {
|
||||
sidebar.classList.toggle('collapsed');
|
||||
|
||||
// 在小屏幕上显示/隐藏遮罩层
|
||||
if (window.innerWidth <= 768) {
|
||||
if (sidebar.classList.contains('collapsed')) {
|
||||
if (overlay) {
|
||||
overlay.classList.remove('active');
|
||||
}
|
||||
mainContent.classList.add('expanded');
|
||||
} else {
|
||||
if (overlay) {
|
||||
overlay.classList.add('active');
|
||||
}
|
||||
mainContent.classList.remove('expanded');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onResize() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const mainContent = document.getElementById('mainContent');
|
||||
const overlay = document.getElementById('overlay');
|
||||
|
||||
if (window.innerWidth > 768) {
|
||||
// 大屏幕:确保侧边栏展开
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('collapsed');
|
||||
}
|
||||
if (mainContent) {
|
||||
mainContent.classList.remove('expanded');
|
||||
}
|
||||
if (overlay) {
|
||||
overlay.classList.remove('active');
|
||||
}
|
||||
} else {
|
||||
// 小屏幕:默认折叠侧边栏
|
||||
if (sidebar && !sidebar.classList.contains('collapsed')) {
|
||||
sidebar.classList.add('collapsed');
|
||||
}
|
||||
if (mainContent) {
|
||||
mainContent.classList.add('expanded');
|
||||
}
|
||||
if (overlay) {
|
||||
overlay.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isHomePage(): boolean {
|
||||
return this.router.url === '/';
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { NewPageComponent } from './new-page/new-page.component';
|
||||
import { DevuiFormComponent } from './devui-form/devui-form.component';
|
||||
|
||||
export const routes: Routes = [];
|
||||
export const routes: Routes = [
|
||||
{ path: 'new-page', component: NewPageComponent },
|
||||
{ path: 'devui-form', component: DevuiFormComponent },
|
||||
];
|
||||
|
||||
323
src/app/devui-form/devui-form.component.ts
Normal file
323
src/app/devui-form/devui-form.component.ts
Normal file
@@ -0,0 +1,323 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Observable, of, timer } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-devui-form',
|
||||
standalone: true,
|
||||
imports: [ReactiveFormsModule, CommonModule, RouterLink],
|
||||
template: `
|
||||
<div class="container">
|
||||
<a routerLink="/" class="back-link">← 返回首页</a>
|
||||
<h1>DevUI 表单示例</h1>
|
||||
<p>根据《组件使用进阶-表单的邪修使用与避坑指南》文档实现的示例</p>
|
||||
|
||||
<form [formGroup]="userForm" (ngSubmit)="onSubmit()" class="form-container">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名 *</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
formControlName="username"
|
||||
class="form-control"
|
||||
[class.error]="userForm.get('username')?.invalid && userForm.get('username')?.touched">
|
||||
<div class="error-message" *ngIf="userForm.get('username')?.invalid && userForm.get('username')?.touched">
|
||||
<div *ngIf="userForm.get('username')?.errors?.['required']">用户名是必填项</div>
|
||||
<div *ngIf="userForm.get('username')?.errors?.['minlength']">用户名至少需要3个字符</div>
|
||||
<div *ngIf="userForm.get('username')?.errors?.['usernameTaken']">用户名已被占用</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">邮箱 *</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
formControlName="email"
|
||||
class="form-control"
|
||||
[class.error]="userForm.get('email')?.invalid && userForm.get('email')?.touched">
|
||||
<div class="error-message" *ngIf="userForm.get('email')?.invalid && userForm.get('email')?.touched">
|
||||
<div *ngIf="userForm.get('email')?.errors?.['required']">邮箱是必填项</div>
|
||||
<div *ngIf="userForm.get('email')?.errors?.['email']">请输入有效的邮箱地址</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="age">年龄 *</label>
|
||||
<input
|
||||
id="age"
|
||||
type="number"
|
||||
formControlName="age"
|
||||
class="form-control"
|
||||
[class.error]="userForm.get('age')?.invalid && userForm.get('age')?.touched">
|
||||
<div class="error-message" *ngIf="userForm.get('age')?.invalid && userForm.get('age')?.touched">
|
||||
<div *ngIf="userForm.get('age')?.errors?.['required']">年龄是必填项</div>
|
||||
<div *ngIf="userForm.get('age')?.errors?.['min']">年龄不能小于18岁</div>
|
||||
<div *ngIf="userForm.get('age')?.errors?.['max']">年龄不能大于100岁</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="department">部门 *</label>
|
||||
<select
|
||||
id="department"
|
||||
formControlName="department"
|
||||
class="form-control"
|
||||
[class.error]="userForm.get('department')?.invalid && userForm.get('department')?.touched">
|
||||
<option value="">请选择部门</option>
|
||||
<option value="engineering">工程部</option>
|
||||
<option value="marketing">市场部</option>
|
||||
<option value="sales">销售部</option>
|
||||
<option value="hr">人事部</option>
|
||||
</select>
|
||||
<div class="error-message" *ngIf="userForm.get('department')?.invalid && userForm.get('department')?.touched">
|
||||
<div *ngIf="userForm.get('department')?.errors?.['required']">部门是必选项</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="showAdminFields">
|
||||
<label for="adminCode">管理员代码 *</label>
|
||||
<input
|
||||
id="adminCode"
|
||||
type="text"
|
||||
formControlName="adminCode"
|
||||
class="form-control"
|
||||
[class.error]="userForm.get('adminCode')?.invalid && userForm.get('adminCode')?.touched">
|
||||
<div class="error-message" *ngIf="userForm.get('adminCode')?.invalid && userForm.get('adminCode')?.touched">
|
||||
<div *ngIf="userForm.get('adminCode')?.errors?.['required']">管理员代码是必填项</div>
|
||||
<div *ngIf="userForm.get('adminCode')?.errors?.['minlength']">管理员代码至少需要6个字符</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
formControlName="isAdmin"
|
||||
(change)="toggleAdminFields($event)">
|
||||
是否为管理员
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" [disabled]="userForm.invalid" class="btn btn-primary">
|
||||
提交
|
||||
</button>
|
||||
<button type="button" (click)="resetForm()" class="btn btn-secondary">
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="form-data" *ngIf="submittedData">
|
||||
<h3>提交的数据:</h3>
|
||||
<pre>{{ submittedData | json }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 1rem;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-control.error {
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
font-weight: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkbox-group input {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-data {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #e9ecef;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-data pre {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class DevuiFormComponent implements OnInit {
|
||||
userForm: FormGroup;
|
||||
showAdminFields = false;
|
||||
submittedData: any = null;
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.userForm = this.fb.group({
|
||||
username: ['', [Validators.required, Validators.minLength(3)]],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
age: ['', [Validators.required, Validators.min(18), Validators.max(100)]],
|
||||
department: ['', Validators.required],
|
||||
isAdmin: [false]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// 监听用户名变化以演示异步验证
|
||||
this.userForm.get('username')?.valueChanges.subscribe(value => {
|
||||
console.log('Username changed:', value);
|
||||
});
|
||||
}
|
||||
|
||||
// 动态添加/删除管理员字段
|
||||
toggleAdminFields(event: any) {
|
||||
const isChecked = event.target.checked;
|
||||
this.showAdminFields = isChecked;
|
||||
|
||||
if (isChecked) {
|
||||
this.userForm.addControl('adminCode',
|
||||
this.fb.control('', [Validators.required, Validators.minLength(6)]));
|
||||
} else {
|
||||
this.userForm.removeControl('adminCode');
|
||||
}
|
||||
}
|
||||
|
||||
// 异步验证器模拟
|
||||
uniqueUsernameValidator() {
|
||||
return (control: any) => {
|
||||
if (!control.value) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
// 模拟服务端检查用户名是否已存在
|
||||
const isTaken = control.value.toLowerCase() === 'admin' ||
|
||||
control.value.toLowerCase() === 'user' ||
|
||||
control.value.toLowerCase() === 'test';
|
||||
|
||||
// 模拟网络延迟
|
||||
return timer(500).pipe(
|
||||
map(() => isTaken ? { usernameTaken: true } : null)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.userForm.valid) {
|
||||
this.submittedData = this.userForm.value;
|
||||
console.log('Form submitted:', this.userForm.value);
|
||||
} else {
|
||||
// 标记所有字段为已触碰以显示验证错误
|
||||
this.markFormGroupTouched();
|
||||
}
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.userForm.reset({
|
||||
isAdmin: false
|
||||
});
|
||||
this.showAdminFields = false;
|
||||
this.submittedData = null;
|
||||
}
|
||||
|
||||
private markFormGroupTouched() {
|
||||
Object.keys(this.userForm.controls).forEach(key => {
|
||||
const control = this.userForm.get(key);
|
||||
control?.markAsTouched();
|
||||
});
|
||||
}
|
||||
}
|
||||
71
src/app/new-page/new-page.component.ts
Normal file
71
src/app/new-page/new-page.component.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-new-page',
|
||||
standalone: true,
|
||||
imports: [RouterLink],
|
||||
template: `
|
||||
<div class="container">
|
||||
<h1>新页面</h1>
|
||||
<p>这是通过侧边栏菜单访问的新页面</p>
|
||||
<div class="content">
|
||||
<p>此页面展示了管理后台中的一个典型内容页面。您可以在这里放置表单、数据表格或其他业务功能。</p>
|
||||
<div class="card">
|
||||
<h2>功能示例</h2>
|
||||
<ul>
|
||||
<li>数据展示</li>
|
||||
<li>表单输入</li>
|
||||
<li>图表显示</li>
|
||||
<li>操作按钮</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.container {
|
||||
padding: 1rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class NewPageComponent {
|
||||
constructor() { }
|
||||
}
|
||||
12
src/main.ts
12
src/main.ts
@@ -2,5 +2,17 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
import {
|
||||
devuiLightTheme,
|
||||
ThemeServiceInit
|
||||
} from 'ng-devui/theme';
|
||||
|
||||
import { infinityTheme } from 'ng-devui/theme-collection';
|
||||
|
||||
ThemeServiceInit({
|
||||
'devui-light-theme': devuiLightTheme,
|
||||
'infinity-theme': infinityTheme,
|
||||
}, 'infinity-theme');
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
391
组件使用进阶-表单的邪修使用与避坑指南.md
Normal file
391
组件使用进阶-表单的邪修使用与避坑指南.md
Normal file
@@ -0,0 +1,391 @@
|
||||
DevUI 组件使用进阶:表单深度用法与避坑指南
|
||||
---
|
||||
表单作为用户与系统交互的核心界面,其设计直接影响用户体验和数据质量。本文将深入探讨 DevUI 表单组件的高级用法和常见陷阱,帮助开发攻城狮们构建更高效、更可靠的表单应用。
|
||||
|
||||
## 表单组件核心特性深度解析
|
||||
|
||||
DevUI 表单组件(d-form)不仅仅是一个简单的数据收集容器,它是一套完整的数据验证和管理解决方案。其设计充分考虑了企业级应用场景下的各种复杂需求。
|
||||
|
||||
### 响应式表单与动态验证
|
||||
|
||||
DevUI 表单组件支持强大的响应式表单功能,可以实现复杂的动态验证逻辑:
|
||||
|
||||
```typescript
|
||||
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
|
||||
|
||||
export class DynamicFormComponent {
|
||||
userForm: FormGroup;
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.userForm = this.fb.group({
|
||||
username: ['', [Validators.required, Validators.minLength(3)]],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
age: ['', [Validators.required, Validators.min(18), Validators.max(100)]],
|
||||
department: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
// 动态添加验证规则
|
||||
toggleAdminFields(isAdmin: boolean) {
|
||||
if (isAdmin) {
|
||||
this.userForm.addControl('adminCode',
|
||||
this.fb.control('', [Validators.required, Validators.minLength(6)]));
|
||||
} else {
|
||||
this.userForm.removeControl('adminCode');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在实际应用中,需要注意几个关键点:
|
||||
1. 动态添加/删除表单控件时要确保验证状态正确更新
|
||||
2. 使用 `updateValueAndValidity()` 方法在必要时手动触发验证更新
|
||||
3. 合理使用 `setValue()` 和 `patchValue()` 方法更新表单值
|
||||
|
||||
### 异步验证与防抖处理
|
||||
|
||||
对于需要服务端验证的场景(如用户名唯一性检查),DevUI 表单支持异步验证:
|
||||
|
||||
```typescript
|
||||
import { AbstractControl, AsyncValidatorFn } from '@angular/forms';
|
||||
import { Observable, of, timer } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
export class AsyncValidationComponent {
|
||||
// 异步验证器
|
||||
uniqueUsernameValidator(): AsyncValidatorFn {
|
||||
return (control: AbstractControl): Observable<{[key: string]: any} | null> => {
|
||||
if (!control.value) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
// 防抖处理,避免频繁请求
|
||||
return timer(500).pipe(
|
||||
switchMap(() => this.userService.checkUsernameExists(control.value)),
|
||||
map(exists => exists ? { usernameTaken: true } : null)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.userForm = this.fb.group({
|
||||
username: ['',
|
||||
[Validators.required],
|
||||
[this.uniqueUsernameValidator()]
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 表单布局与复杂结构处理
|
||||
|
||||
### 响应式布局与栅格系统
|
||||
|
||||
DevUI 表单组件与栅格系统紧密结合,可以实现灵活的响应式布局:
|
||||
|
||||
```html
|
||||
<d-form [formGroup]="userForm" [layout]="formLayout">
|
||||
<d-row [gutter]="12">
|
||||
<d-col [span]="24" *ngIf="isMobile">
|
||||
<d-form-item>
|
||||
<d-form-label required>用户名</d-form-label>
|
||||
<d-form-control>
|
||||
<input dTextInput formControlName="username" />
|
||||
</d-form-control>
|
||||
</d-form-item>
|
||||
</d-col>
|
||||
|
||||
<d-col [span]="12" *ngIf="!isMobile">
|
||||
<d-form-item>
|
||||
<d-form-label required>用户名</d-form-label>
|
||||
<d-form-control>
|
||||
<input dTextInput formControlName="username" />
|
||||
</d-form-control>
|
||||
</d-form-item>
|
||||
</d-col>
|
||||
|
||||
<d-col [span]="12" *ngIf="!isMobile">
|
||||
<d-form-item>
|
||||
<d-form-label required>邮箱</d-form-label>
|
||||
<d-form-control>
|
||||
<input dTextInput formControlName="email" type="email" />
|
||||
</d-form-control>
|
||||
</d-form-item>
|
||||
</d-col>
|
||||
</d-row>
|
||||
</d-form>
|
||||
```
|
||||
|
||||
### 嵌套表单与数组结构
|
||||
|
||||
处理复杂数据结构(如地址列表、工作经历等)时,可以使用 FormArray:
|
||||
|
||||
```typescript
|
||||
export class NestedFormComponent {
|
||||
userForm: FormGroup;
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
this.userForm = this.fb.group({
|
||||
basicInfo: this.fb.group({
|
||||
firstName: ['', Validators.required],
|
||||
lastName: ['', Validators.required]
|
||||
}),
|
||||
addresses: this.fb.array([
|
||||
this.createAddressGroup()
|
||||
]),
|
||||
experiences: this.fb.array([])
|
||||
});
|
||||
}
|
||||
|
||||
createAddressGroup(): FormGroup {
|
||||
return this.fb.group({
|
||||
street: ['', Validators.required],
|
||||
city: ['', Validators.required],
|
||||
zipCode: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]]
|
||||
});
|
||||
}
|
||||
|
||||
get addresses(): FormArray {
|
||||
return this.userForm.get('addresses') as FormArray;
|
||||
}
|
||||
|
||||
addAddress() {
|
||||
this.addresses.push(this.createAddressGroup());
|
||||
}
|
||||
|
||||
removeAddress(index: number) {
|
||||
this.addresses.removeAt(index);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 表单状态管理与用户体验优化
|
||||
|
||||
### 表单状态跟踪与提交控制
|
||||
|
||||
合理管理表单状态可以显著提升用户体验:
|
||||
|
||||
```typescript
|
||||
export class FormStateComponent {
|
||||
isSubmitting = false;
|
||||
submitSuccess = false;
|
||||
|
||||
onSubmit() {
|
||||
// 防止重复提交
|
||||
if (this.isSubmitting || this.userForm.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSubmitting = true;
|
||||
this.submitSuccess = false;
|
||||
|
||||
// 标记所有字段为已触碰,显示验证错误
|
||||
this.markFormGroupTouched(this.userForm);
|
||||
|
||||
this.userService.saveUser(this.userForm.value).subscribe({
|
||||
next: (response) => {
|
||||
this.isSubmitting = false;
|
||||
this.submitSuccess = true;
|
||||
this.showMessage('用户信息保存成功', 'success');
|
||||
},
|
||||
error: (error) => {
|
||||
this.isSubmitting = false;
|
||||
this.showMessage('保存失败: ' + error.message, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private markFormGroupTouched(formGroup: FormGroup) {
|
||||
Object.keys(formGroup.controls).forEach(key => {
|
||||
const control = formGroup.get(key);
|
||||
if (control instanceof FormGroup) {
|
||||
this.markFormGroupTouched(control);
|
||||
} else {
|
||||
control.markAsTouched();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义验证器与错误提示
|
||||
|
||||
创建自定义验证器来处理特定业务规则:
|
||||
|
||||
```typescript
|
||||
import { ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
|
||||
|
||||
// 密码强度验证器
|
||||
export function passwordStrengthValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const value = control.value;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errors: any = {};
|
||||
|
||||
// 至少8个字符
|
||||
if (value.length < 8) {
|
||||
errors.minLength = true;
|
||||
}
|
||||
|
||||
// 包含大写字母
|
||||
if (!/[A-Z]/.test(value)) {
|
||||
errors.uppercaseRequired = true;
|
||||
}
|
||||
|
||||
// 包含小写字母
|
||||
if (!/[a-z]/.test(value)) {
|
||||
errors.lowercaseRequired = true;
|
||||
}
|
||||
|
||||
// 包含数字
|
||||
if (!/\d/.test(value)) {
|
||||
errors.numberRequired = true;
|
||||
}
|
||||
|
||||
// 包含特殊字符
|
||||
if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(value)) {
|
||||
errors.specialCharRequired = true;
|
||||
}
|
||||
|
||||
return Object.keys(errors).length > 0 ? errors : null;
|
||||
};
|
||||
}
|
||||
|
||||
// 使用自定义验证器
|
||||
export class PasswordFormComponent {
|
||||
passwordForm = this.fb.group({
|
||||
password: ['', [Validators.required, passwordStrengthValidator()]],
|
||||
confirmPassword: ['', Validators.required]
|
||||
}, {
|
||||
validators: this.passwordMatchValidator
|
||||
});
|
||||
|
||||
private passwordMatchValidator(form: FormGroup) {
|
||||
const password = form.get('password');
|
||||
const confirmPassword = form.get('confirmPassword');
|
||||
|
||||
if (password.value !== confirmPassword.value) {
|
||||
confirmPassword.setErrors({ passwordMismatch: true });
|
||||
} else {
|
||||
confirmPassword.setErrors(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常见陷阱与解决方案
|
||||
|
||||
### 内存泄漏与订阅管理
|
||||
|
||||
不当的表单订阅可能导致内存泄漏:
|
||||
|
||||
```typescript
|
||||
export class SafeFormComponent implements OnInit, OnDestroy {
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
ngOnInit() {
|
||||
// 正确的订阅方式 - 保存订阅引用
|
||||
const valueChangesSubscription = this.userForm.valueChanges.subscribe(
|
||||
value => this.handleFormChanges(value)
|
||||
);
|
||||
this.subscriptions.push(valueChangesSubscription);
|
||||
|
||||
// 状态变化订阅
|
||||
const statusChangesSubscription = this.userForm.statusChanges.subscribe(
|
||||
status => this.handleFormStatus(status)
|
||||
);
|
||||
this.subscriptions.push(statusChangesSubscription);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// 组件销毁时取消所有订阅
|
||||
this.subscriptions.forEach(sub => sub.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 表单重置与数据同步
|
||||
|
||||
正确处理表单重置和数据同步问题:
|
||||
|
||||
```typescript
|
||||
export class FormResetComponent {
|
||||
originalData: any;
|
||||
|
||||
// 加载数据并初始化表单
|
||||
loadData(userId: string) {
|
||||
this.userService.getUser(userId).subscribe(user => {
|
||||
this.originalData = { ...user };
|
||||
this.userForm.reset(user);
|
||||
});
|
||||
}
|
||||
|
||||
// 重置表单到初始状态
|
||||
resetForm() {
|
||||
this.userForm.reset(this.originalData);
|
||||
}
|
||||
|
||||
// 取消编辑并返回原始数据
|
||||
cancelEdit() {
|
||||
this.userForm.setValue(this.originalData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 性能优化技巧
|
||||
|
||||
1. 避免在模板中进行复杂计算:
|
||||
|
||||
```typescript
|
||||
// ❌ 不推荐 - 在模板中进行复杂计算
|
||||
// <d-form-item [hasFeedback]="isPasswordStrong(form.get('password').value)">
|
||||
// <input dTextInput formControlName="password" />
|
||||
// </d-form-item>
|
||||
|
||||
// ✅ 推荐 - 在组件中预计算
|
||||
export class OptimizedFormComponent {
|
||||
isPasswordStrong: boolean = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.passwordForm.get('password').valueChanges.subscribe(value => {
|
||||
this.isPasswordStrong = this.checkPasswordStrength(value);
|
||||
});
|
||||
}
|
||||
|
||||
private checkPasswordStrength(password: string): boolean {
|
||||
// 复杂的密码强度检查逻辑
|
||||
return password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. 使用 OnPush 策略优化变更检测:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-optimized-form',
|
||||
templateUrl: './optimized-form.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class OptimizedFormComponent {
|
||||
@Input() formData: any;
|
||||
|
||||
// 当数据发生变化时手动触发变更检测
|
||||
ngOnChanges() {
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 结语
|
||||
|
||||
DevUI 表单组件作为用户交互的核心组件,其强大功能和灵活性为开发者提供了丰富的可能性。通过深入理解其工作机制,合理运用高级特性,并规避常见陷阱,我们可以构建出高性能、高可用的企业级表单应用。
|
||||
|
||||
在实际项目中,建议根据具体业务需求选择合适的功能组合,避免过度设计。同时,持续关注 DevUI 的更新和最佳实践,不断提升开发技能和产品质量。只有在实践中不断总结和完善,才能真正掌握 DevUI 表单组件的精髓,为企业级应用开发提供有力支撑。
|
||||
Reference in New Issue
Block a user