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;