diff --git a/angular.json b/angular.json
index 5a4cb58..5cb2186 100644
--- a/angular.json
+++ b/angular.json
@@ -29,9 +29,15 @@
{
"glob": "**/*",
"input": "public"
+ },
+ {
+ "glob": "**/*",
+ "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
+ "output": "/assets/"
}
],
"styles": [
+ "src/theme.less",
"src/styles.scss"
],
"scripts": []
diff --git a/package-lock.json b/package-lock.json
index c1973d9..6d6954c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"@angular/platform-browser": "^19.1.0",
"@angular/platform-browser-dynamic": "^19.1.0",
"@angular/router": "^19.1.0",
+ "ng-zorro-antd": "^19.0.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -417,6 +418,22 @@
}
}
},
+ "node_modules/@angular/cdk": {
+ "version": "19.1.5",
+ "resolved": "https://registry.npmmirror.com/@angular/cdk/-/cdk-19.1.5.tgz",
+ "integrity": "sha512-+g20LIvYHThKBD6oXTYWVL6+ecaOWtPJu08R5xIfGrwXoj0l/9prLwuSW8GlIATI3mDkSesyhQsomb9jAUzKwQ==",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "optionalDependencies": {
+ "parse5": "^7.1.2"
+ },
+ "peerDependencies": {
+ "@angular/common": "^19.0.0 || ^20.0.0",
+ "@angular/core": "^19.0.0 || ^20.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/cli": {
"version": "19.1.8",
"resolved": "https://registry.npmmirror.com/@angular/cli/-/cli-19.1.8.tgz",
@@ -599,6 +616,40 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
+ "node_modules/@ant-design/colors": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.0.tgz",
+ "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@ant-design/icons-angular": {
+ "version": "19.0.0",
+ "resolved": "https://registry.npmmirror.com/@ant-design/icons-angular/-/icons-angular-19.0.0.tgz",
+ "integrity": "sha512-bBWFA1cTZwLAFTgpozkeNIHX1nXyZuiUaRzTcAfFEt85eW1X3ypMcBfS/XEVVVzkdTw5Td+E1vwzgfuUlKiYSA==",
+ "dependencies": {
+ "@ant-design/colors": "^7.0.0",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^19.0.0",
+ "@angular/core": "^19.0.0",
+ "@angular/platform-browser": "^19.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz",
@@ -2047,7 +2098,6 @@
"version": "7.26.0",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
- "dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -2125,6 +2175,14 @@
"node": ">=0.1.90"
}
},
+ "node_modules/@ctrl/tinycolor": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+ "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz",
@@ -6318,6 +6376,21 @@
"integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==",
"dev": true
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "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.npmmirror.com/date-format/-/date-format-4.0.14.tgz",
@@ -6673,7 +6746,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=0.12"
},
@@ -9679,6 +9752,26 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "node_modules/ng-zorro-antd": {
+ "version": "19.0.2",
+ "resolved": "https://registry.npmmirror.com/ng-zorro-antd/-/ng-zorro-antd-19.0.2.tgz",
+ "integrity": "sha512-F0Ct4cJEXpEAAIIirviw5wt6njoITp9jfPq8Tsv3JY6bOriaPQ/6dUgnsXKMAQ7ocoi1hpykStwAMmAQwGIVog==",
+ "dependencies": {
+ "@angular/cdk": "^19.0.0",
+ "@ant-design/icons-angular": "^19.0.0",
+ "@ctrl/tinycolor": "^3.6.0",
+ "date-fns": "^2.16.1",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/animations": "^19.0.0",
+ "@angular/common": "^19.0.0",
+ "@angular/core": "^19.0.0",
+ "@angular/forms": "^19.0.0",
+ "@angular/platform-browser": "^19.0.0",
+ "@angular/router": "^19.0.0"
+ }
+ },
"node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-6.1.0.tgz",
@@ -10347,7 +10440,7 @@
"version": "7.2.1",
"resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.2.1.tgz",
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"entities": "^4.5.0"
},
@@ -10852,8 +10945,7 @@
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regenerator-transform": {
"version": "0.15.2",
diff --git a/package.json b/package.json
index 1233016..c4a74d2 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@angular/platform-browser": "^19.1.0",
"@angular/platform-browser-dynamic": "^19.1.0",
"@angular/router": "^19.1.0",
+ "ng-zorro-antd": "^19.0.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -35,4 +36,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.7.2"
}
-}
+}
\ No newline at end of file
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 36093e1..0680b43 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,336 +1 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Hello, {{ title }}
-
Congratulations! Your app is running. 🎉
-
-
-
-
- @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) {
-
- {{ item.title }}
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index e69de29..673f44c 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -0,0 +1,6 @@
+body {
+ font-family: Arial, sans-serif;
+ background-color: #f0f0f0;
+ margin: 0;
+ padding: 0;
+}
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index a1e7d6f..cb59249 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,8 +1,16 @@
-import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
+import { zh_CN, provideNzI18n } from 'ng-zorro-antd/i18n';
+import { registerLocaleData } from '@angular/common';
+import zh from '@angular/common/locales/zh';
+import { FormsModule } from '@angular/forms';
+import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
+import { provideHttpClient } from '@angular/common/http';
+
+registerLocaleData(zh);
export const appConfig: ApplicationConfig = {
- providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
+ providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideNzI18n(zh_CN), importProvidersFrom(FormsModule), provideAnimationsAsync(), provideHttpClient()]
};
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
new file mode 100644
index 0000000..47e02fb
--- /dev/null
+++ b/src/app/app.module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+import { AppComponent } from './app.component';
+import { ResourcePipe } from './resource.pipe';
+import { LoginComponent } from './login/login.component';
+import { CommonModule } from '@angular/common';
+
+@NgModule({
+ declarations: [
+
+ ],
+ imports: [
+ CommonModule,
+ BrowserModule,
+ FormsModule
+ ],
+ providers: []
+})
+export class AppModule { }
\ No newline at end of file
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index dc39edb..93aea74 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,3 +1,8 @@
import { Routes } from '@angular/router';
-export const routes: Routes = [];
+export const routes: Routes = [
+ {
+ path: '',
+ loadComponent: () => import('./login/login.component').then((m) => m.LoginComponent),
+ }
+];
diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html
new file mode 100644
index 0000000..1291cee
--- /dev/null
+++ b/src/app/login/login.component.html
@@ -0,0 +1,21 @@
+
+
{{ 'loginTitle' | resource }}
+
+
\ No newline at end of file
diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss
new file mode 100644
index 0000000..a269741
--- /dev/null
+++ b/src/app/login/login.component.scss
@@ -0,0 +1,68 @@
+//导入全局变量
+@import '../../styles.scss';
+.login-panel {
+ max-width: 400px;
+ margin: 50px auto;
+ padding: 20px;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ background-color: $panel-background-color; // 使用全局变量定义的面板背景色
+
+ h2 {
+ text-align: center;
+ color: $text-color; // 使用全局变量定义的文本颜色
+ }
+
+ .form-group {
+ margin-bottom: 15px;
+
+ label {
+ display: block;
+ margin-bottom: 5px;
+ color: $text-color; // 使用全局变量定义的文本颜色
+ }
+
+ input {
+ width: 100%;
+ padding: 8px;
+ box-sizing: border-box;
+ background-color: $panel-background-color; // 使用全局变量定义的面板背景色
+ color: $text-color; // 使用全局变量定义的文本颜色
+ border: 1px solid $disabled-color; // 使用全局变量定义的禁用颜色
+ }
+ }
+
+ .captcha-container {
+ display: flex;
+ align-items: center;
+
+ input {
+ flex: 1;
+ margin-right: 10px;
+ }
+
+ img {
+ cursor: pointer;
+ border: 1px solid $disabled-color; // 使用全局变量定义的禁用颜色
+ border-radius: 3px;
+ }
+ }
+
+ button {
+ width: 100%;
+ padding: 10px;
+ background-color: $selected-color; // 使用全局变量定义的选中颜色
+ color: $text-color; // 使用全局变量定义的文本颜色
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: darken($selected-color, 10%); // 使用全局变量定义的选中颜色并加深
+ }
+ &:disabled {
+ background-color: $disabled-color; // 使用全局变量定义的禁用颜色
+ cursor: not-allowed;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts
new file mode 100644
index 0000000..3b50edb
--- /dev/null
+++ b/src/app/login/login.component.ts
@@ -0,0 +1,121 @@
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { ResourcePipe } from '../resource.pipe';
+
+@Component({
+ selector: 'app-login',
+ templateUrl: './login.component.html',
+ styleUrls: ['./login.component.scss'],
+ standalone: true,
+ imports: [
+ CommonModule,
+ FormsModule, // 导入FormsModule以支持双向数据绑定
+ ResourcePipe,
+ ],
+})
+export class LoginComponent {
+ username: string = '';
+ password: string = '';
+ captcha: string = '';
+ captchaAnswer: string = '';
+ captchaImage: string = '';
+
+ constructor() {
+ this.refreshCaptcha();
+ }
+
+ refreshCaptcha(): void {
+ // 这里可以调用后端API获取新的验证码图片URL
+ this.captchaImage = this.genRandomStringImage(4);
+ }
+ genRandomStringImage(length: number): string {
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let result = '';
+ for (let i = 0; i < length; i++) {
+ const randomIndex = Math.floor(Math.random() * characters.length);
+ result += characters.charAt(randomIndex);
+ }
+
+ this.captchaAnswer = result;
+ // 创建 canvas 元素
+ const canvas = document.createElement('canvas');
+ canvas.width = 100;
+ canvas.height = 30;
+ const context = canvas.getContext('2d');
+ if (!context) {
+ return '';
+ }
+
+ // 设置背景颜色
+ context.fillStyle = '#000';
+ context.fillRect(0, 0, canvas.width, canvas.height);
+
+ // 添加干扰线
+ context.strokeStyle = '#fff';
+ context.lineWidth = 1;
+ for (let i = 0; i < 5; i++) {
+ context.beginPath();
+ context.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);
+ context.lineTo(Math.random() * canvas.width, Math.random() * canvas.height);
+ context.stroke();
+ }
+
+ // 设置文字颜色和字体
+ context.fillStyle = '#fff';
+ context.font = '20px Arial';
+
+ // 添加文字扭曲
+ context.save();
+ context.translate(10, 20);
+ context.rotate((Math.random() - 0.5) * 0.1); // 轻微旋转
+ context.fillText(result, 0, 0);
+ context.restore();
+
+ // 将 canvas 内容转换为 base64 URL
+ const base64Url = canvas.toDataURL('image/png');
+ return base64Url;
+}
+
+ onSubmit(): void {
+ // 处理登录逻辑
+ const data = {
+ username: this.username,
+ password: this.encryptPassword(this.password)
+ }
+ // post to /api/login
+ fetch('/api/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data)
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.code === 0) {
+ // 登录成功,跳转到首页
+ console.log('登录成功');
+ } else {
+ // 登录失败,显示错误信息
+ }
+
+ })
+ }
+
+ captchaIsCorrect(): boolean {
+ return this.captcha.toLowerCase() === this.captchaAnswer.toLowerCase();
+ }
+
+ encryptPassword(password: string): string {
+ // 这里可以添加密码加密逻辑
+ const plaintext = `${password}${Date.now()}`
+ let ciphertext = '';
+ for (let i = 0; i < plaintext.length; i++) {
+ const d = plaintext.charCodeAt(i)^0xFF;
+ ciphertext += d.toString(16);
+ }
+ return ciphertext;
+ // return password;
+ }
+}
\ No newline at end of file
diff --git a/src/app/resource.pipe.ts b/src/app/resource.pipe.ts
new file mode 100644
index 0000000..703ccdc
--- /dev/null
+++ b/src/app/resource.pipe.ts
@@ -0,0 +1,11 @@
+import { Pipe, PipeTransform } from '@angular/core';
+import { resources } from './resources';
+
+@Pipe({
+ name: 'resource'
+})
+export class ResourcePipe implements PipeTransform {
+ transform(key: string): string {
+ return resources[key] || '';
+ }
+}
\ No newline at end of file
diff --git a/src/app/resources.ts b/src/app/resources.ts
new file mode 100644
index 0000000..681f76e
--- /dev/null
+++ b/src/app/resources.ts
@@ -0,0 +1,8 @@
+export const resources:{[key:string]:string} = {
+ Title: 'Gohttpd后台管理',
+ loginTitle: '登录',
+ usernameLabel: '用户名',
+ passwordLabel: '密码',
+ captchaLabel: '验证码',
+ loginButton: '登录'
+};
\ No newline at end of file
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee0..2120bea 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,32 @@
/* You can add global styles to this file, and also import other style files */
+/* styles.scss */
+
+/* 定义全局变量 */
+$background-color: #1E1E1E;
+$panel-background-color: #2D2D2D;
+$text-color: #FFFFFF;
+$selected-color: #007BFF;
+$disabled-color: #6C757D;
+
+/* 应用到全局样式 */
+body {
+ background-color: $background-color;
+ color: $text-color;
+}
+
+.panel {
+ background-color: $panel-background-color;
+ color: $text-color;
+}
+
+.selected {
+ background-color: $selected-color;
+ color: $text-color;
+}
+
+.disabled {
+ background-color: $disabled-color;
+ color: $text-color;
+ pointer-events: none; /* 禁用交互 */
+ opacity: 0.6; /* 淡化效果 */
+}
\ No newline at end of file
diff --git a/src/theme.less b/src/theme.less
new file mode 100644
index 0000000..ecf2541
--- /dev/null
+++ b/src/theme.less
@@ -0,0 +1,9 @@
+
+// Custom Theming for NG-ZORRO
+// For more information: https://ng.ant.design/docs/customize-theme/en
+@import "../node_modules/ng-zorro-antd/ng-zorro-antd.less";
+
+// Override less variables to here
+// View all variables: https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/components/style/themes/default.less
+
+// @primary-color: #1890ff;