devui开发

This commit is contained in:
maxf
2025-11-26 18:55:05 +08:00
parent b83ecdfe46
commit 315aa622ce
12 changed files with 1630 additions and 455 deletions

7
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#2E2B43",
"titleBar.activeBackground": "#403C5E",
"titleBar.activeForeground": "#FCFBFD"
}
}

View File

@@ -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
View File

@@ -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",

View File

@@ -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"

View File

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

View File

@@ -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>

View File

@@ -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 === '/';
}
}

View File

@@ -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 },
];

View 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();
});
}
}

View 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() { }
}

View File

@@ -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));

View 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 表单组件的精髓,为企业级应用开发提供有力支撑。