diff --git a/angular.json b/angular.json index 9b72354..247d212 100644 --- a/angular.json +++ b/angular.json @@ -129,7 +129,10 @@ } }, "cli": { - "schematicCollections": ["@ionic/angular-toolkit"] + "schematicCollections": [ + "@ionic/angular-toolkit" + ], + "analytics": false }, "schematics": { "@ionic/angular-toolkit:component": { diff --git a/capacitor.config.ts b/capacitor.config.ts index 9d724eb..184d8d5 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -1,8 +1,8 @@ import type { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { - appId: 'io.ionic.starter', - appName: 'FunGame.Web', + appId: 'com.milimoe.fungame', + appName: 'FunGame', webDir: 'www' }; diff --git a/ionic.config.json b/ionic.config.json index 8d8dabe..f15f374 100644 --- a/ionic.config.json +++ b/ionic.config.json @@ -1,5 +1,5 @@ { - "name": "FunGame.Web", + "name": "FunGame", "integrations": { "capacitor": {} }, diff --git a/package-lock.json b/package-lock.json index 86051dc..a4c1718 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "FunGame.Web", - "version": "0.0.1", + "name": "com.milimoe.fungame", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "FunGame.Web", - "version": "0.0.1", + "name": "com.milimoe.fungame", + "version": "1.0.0", "dependencies": { "@angular/animations": "^19.0.0", "@angular/common": "^19.0.0", @@ -16,13 +16,15 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", + "@capacitor/android": "7.2.0", "@capacitor/app": "7.0.1", "@capacitor/core": "7.2.0", "@capacitor/haptics": "7.0.1", "@capacitor/keyboard": "7.0.1", "@capacitor/status-bar": "7.0.1", - "@ionic/angular": "^8.0.0", - "ionicons": "^7.0.0", + "@ionic/angular": "^8.5.7", + "ionicons": "^7.4.0", + "moment": "^2.30.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -2556,6 +2558,15 @@ "node": ">=6.9.0" } }, + "node_modules/@capacitor/android": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/@capacitor/android/-/android-7.2.0.tgz", + "integrity": "sha512-zdhEy3jZPG5Toe/pGzKtDgIiBGywjaoEuQWnGVjBYPlSAEUtAhpZ2At7V0SCb26yluAuzrAUV0Ue+LQeEtHwFQ==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^7.2.0" + } + }, "node_modules/@capacitor/app": { "version": "7.0.1", "resolved": "https://registry.npmmirror.com/@capacitor/app/-/app-7.0.1.tgz", @@ -13173,6 +13184,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", diff --git a/package.json b/package.json index 8dbda71..57deb44 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "FunGame.Web", - "version": "0.0.1", - "author": "Ionic Framework", - "homepage": "https://ionicframework.com/", + "name": "com.milimoe.fungame", + "version": "1.0.0", + "author": "Milimoe", + "homepage": "https://milimoe.com/", "scripts": { "ng": "ng", "start": "ng serve", @@ -21,13 +21,15 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", + "@capacitor/android": "7.2.0", "@capacitor/app": "7.0.1", "@capacitor/core": "7.2.0", "@capacitor/haptics": "7.0.1", "@capacitor/keyboard": "7.0.1", "@capacitor/status-bar": "7.0.1", - "@ionic/angular": "^8.0.0", - "ionicons": "^7.0.0", + "@ionic/angular": "^8.5.7", + "ionicons": "^7.4.0", + "moment": "^2.30.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -60,5 +62,5 @@ "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.6.3" }, - "description": "An Ionic project" + "description": "FunGame Web Version" } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1da531b..eae12ab 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -4,7 +4,7 @@ import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', - imports: [IonApp, IonRouterOutlet], + imports: [IonApp, IonRouterOutlet] }) export class AppComponent { constructor() {} diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 8e70dff..e5b9a7c 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,8 +1,43 @@ import { Routes } from '@angular/router'; +import { TabsPage } from './tabs/tabs.page'; +import { authGuard } from './guards/auth.guard'; export const routes: Routes = [ { - path: '', - loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes), + path: '', + component: TabsPage, + children: [ + { + path: 'home', + loadComponent: () => import('./pages/home/home.page').then((m) => m.HomePage) + }, + { + path: 'feed', + loadComponent: () => import('./pages/feed/feed.page').then((m) => m.FeedPage) + }, + { + path: 'profile', + loadComponent: () => import('./pages/profile/profile.page').then((m) => m.ProfilePage), + canActivate: [authGuard] + }, + { + path: 'login', + loadComponent: () => import('./pages/login/login.page').then((m) => m.LoginPage) + }, + { + path: 'register', + loadComponent: () => import('./pages/register/register.page').then((m) => m.RegisterPage) + }, + { + path: '', + redirectTo: 'home', + pathMatch: 'full', + }, + ] }, + { + path: '', + redirectTo: 'home', + pathMatch: 'full' + } ]; diff --git a/src/app/components/feed-card/feed-card.component.html b/src/app/components/feed-card/feed-card.component.html new file mode 100644 index 0000000..8c2ef3e --- /dev/null +++ b/src/app/components/feed-card/feed-card.component.html @@ -0,0 +1,37 @@ + + + {{ card.title }} + {{ card.author }} + + + 发布于:{{ card.date }} + + +

{{ card.content }}

+
+ +
+ Likes: {{ card.likes }} + + + +
+
+ Forwards: {{ card.forwards }} + + + +
+
+ Comments: {{ card.comments }} + + + +
+
+ + + +
+
+
diff --git a/src/app/tab1/tab1.page.scss b/src/app/components/feed-card/feed-card.component.scss similarity index 100% rename from src/app/tab1/tab1.page.scss rename to src/app/components/feed-card/feed-card.component.scss diff --git a/src/app/components/feed-card/feed-card.component.spec.ts b/src/app/components/feed-card/feed-card.component.spec.ts new file mode 100644 index 0000000..43b1a27 --- /dev/null +++ b/src/app/components/feed-card/feed-card.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { FeedCardComponent } from './feed-card.component'; + +describe('FeedCardComponent', () => { + let component: FeedCardComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ FeedCardComponent ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(FeedCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/feed-card/feed-card.component.ts b/src/app/components/feed-card/feed-card.component.ts new file mode 100644 index 0000000..56e75e3 --- /dev/null +++ b/src/app/components/feed-card/feed-card.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonButton, IonIcon } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { thumbsUpSharp, shareSocialSharp, chatbubbleEllipsesSharp, chevronUpCircleSharp } from 'ionicons/icons'; + +@Component({ + selector: 'app-feed-card', + templateUrl: './feed-card.component.html', + styleUrls: ['./feed-card.component.scss'], + standalone: true, + imports: [IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonButton, IonIcon, CommonModule] +}) +export class FeedCardComponent { + @Input() card: any; + @Output() scrollToTop = new EventEmitter(); + + constructor() { + addIcons({ + 'thumbs-up-sharp': thumbsUpSharp, + 'share-social-sharp': shareSocialSharp, + 'chatbubble-ellipses-sharp': chatbubbleEllipsesSharp, + 'chevron-up-circle-sharp': chevronUpCircleSharp + }); + } + + triggerScrollToTop() { + this.scrollToTop.emit(); + } +} diff --git a/src/app/guards/auth.guard.spec.ts b/src/app/guards/auth.guard.spec.ts new file mode 100644 index 0000000..4ae275e --- /dev/null +++ b/src/app/guards/auth.guard.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { CanActivateFn } from '@angular/router'; + +import { authGuard } from './auth.guard'; + +describe('authGuard', () => { + const executeGuard: CanActivateFn = (...guardParameters) => + TestBed.runInInjectionContext(() => authGuard(...guardParameters)); + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should be created', () => { + expect(executeGuard).toBeTruthy(); + }); +}); diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 0000000..0eb770e --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,15 @@ +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import { AuthService } from '../services/auth.service'; + +export const authGuard: CanActivateFn = (route, state) => { + const authService = inject(AuthService); + const router = inject(Router); + + if (authService.isLoggedIn()) { + return true; + } else { + router.navigateByUrl('/login'); + return false; + } +}; diff --git a/src/app/pages/feed/feed.page.html b/src/app/pages/feed/feed.page.html new file mode 100644 index 0000000..bceac72 --- /dev/null +++ b/src/app/pages/feed/feed.page.html @@ -0,0 +1,40 @@ + + + 动态 + + + + + + + + + + 个人竞技模式(12人混战) + + + + 团队死亡竞赛(12人2队30分胜) + + + + + 显示完整回合日志 + + + +
+ 立即开始一局 FunGame 模拟 >>> + + + +
+ + + + +
diff --git a/src/app/tab2/tab2.page.scss b/src/app/pages/feed/feed.page.scss similarity index 100% rename from src/app/tab2/tab2.page.scss rename to src/app/pages/feed/feed.page.scss diff --git a/src/app/tab1/tab1.page.spec.ts b/src/app/pages/feed/feed.page.spec.ts similarity index 50% rename from src/app/tab1/tab1.page.spec.ts rename to src/app/pages/feed/feed.page.spec.ts index fcc00f9..43c6fd5 100644 --- a/src/app/tab1/tab1.page.spec.ts +++ b/src/app/pages/feed/feed.page.spec.ts @@ -1,13 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FeedPage } from './feed.page'; -import { Tab1Page } from './tab1.page'; +describe('FeedPage', () => { + let component: FeedPage; + let fixture: ComponentFixture; -describe('Tab1Page', () => { - let component: Tab1Page; - let fixture: ComponentFixture; - - beforeEach(async () => { - fixture = TestBed.createComponent(Tab1Page); + beforeEach(() => { + fixture = TestBed.createComponent(FeedPage); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/pages/feed/feed.page.ts b/src/app/pages/feed/feed.page.ts new file mode 100644 index 0000000..51d14fb --- /dev/null +++ b/src/app/pages/feed/feed.page.ts @@ -0,0 +1,56 @@ +import { Component, ViewChild } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonContent, IonHeader, IonTitle, IonToolbar, IonRadioGroup, IonItem, IonLabel, IonRadio, IonCheckbox, IonButton, IonIcon, IonRefresher, IonRefresherContent, IonLoading } from '@ionic/angular/standalone'; +import { FeedService } from '../../services/feed.service'; +import { FeedCardComponent } from '../../components/feed-card/feed-card.component'; +import { addIcons } from 'ionicons'; +import { rocketOutline } from 'ionicons/icons'; + +@Component({ + selector: 'app-feed', + templateUrl: './feed.page.html', + styleUrls: ['./feed.page.scss'], + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonRadioGroup, IonItem, IonLabel, IonRadio, IonCheckbox, IonButton, IonIcon, IonRefresher, IonRefresherContent, IonLoading, CommonModule, FormsModule, FeedCardComponent] +}) +export class FeedPage { + @ViewChild(IonContent) content!: IonContent; + posts: any[] = []; + isTeam: boolean = false; + showAll: boolean = false; + loading: boolean = false; + + constructor(private feedService: FeedService) { + addIcons({ 'rocket-outline': rocketOutline }); + } + + fetchPosts() { + this.loading = true; + this.feedService.fetchPosts(this.isTeam, this.showAll).subscribe({ + next: (posts) => { + this.posts = posts; + this.loading = false; + }, + error: () => { + this.loading = false; + } + }); + } + + doRefresh(event: any) { + this.feedService.fetchPosts(this.isTeam, this.showAll).subscribe({ + next: (posts) => { + this.posts = posts; + event.target.complete(); + }, + error: () => { + event.target.complete(); + } + }); + } + + scrollToTop() { + this.content.scrollToTop(500); + } +} diff --git a/src/app/pages/home/home.page.html b/src/app/pages/home/home.page.html new file mode 100644 index 0000000..c00aa83 --- /dev/null +++ b/src/app/pages/home/home.page.html @@ -0,0 +1,9 @@ + + + 首页 + + + +

欢迎来到首页

+

这是应用的首页内容。

+
diff --git a/src/app/tab3/tab3.page.scss b/src/app/pages/home/home.page.scss similarity index 100% rename from src/app/tab3/tab3.page.scss rename to src/app/pages/home/home.page.scss diff --git a/src/app/tab2/tab2.page.spec.ts b/src/app/pages/home/home.page.spec.ts similarity index 50% rename from src/app/tab2/tab2.page.spec.ts rename to src/app/pages/home/home.page.spec.ts index 92c0cac..fa67f03 100644 --- a/src/app/tab2/tab2.page.spec.ts +++ b/src/app/pages/home/home.page.spec.ts @@ -1,13 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HomePage } from './home.page'; -import { Tab2Page } from './tab2.page'; +describe('HomePage', () => { + let component: HomePage; + let fixture: ComponentFixture; -describe('Tab2Page', () => { - let component: Tab2Page; - let fixture: ComponentFixture; - - beforeEach(async () => { - fixture = TestBed.createComponent(Tab2Page); + beforeEach(() => { + fixture = TestBed.createComponent(HomePage); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/pages/home/home.page.ts b/src/app/pages/home/home.page.ts new file mode 100644 index 0000000..444f09f --- /dev/null +++ b/src/app/pages/home/home.page.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; + +@Component({ + selector: 'app-home', + templateUrl: './home.page.html', + styleUrls: ['./home.page.scss'], + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule] +}) +export class HomePage implements OnInit { + + constructor() { + addIcons({ }); + } + + ngOnInit() { + } + +} diff --git a/src/app/pages/login/login.page.html b/src/app/pages/login/login.page.html new file mode 100644 index 0000000..8418ee5 --- /dev/null +++ b/src/app/pages/login/login.page.html @@ -0,0 +1,23 @@ + + + 登录 + + + + + + + + 邮箱 + + + + 密码 + + + 登录 + 去注册 + + + + diff --git a/src/app/pages/login/login.page.scss b/src/app/pages/login/login.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/tab3/tab3.page.spec.ts b/src/app/pages/login/login.page.spec.ts similarity index 50% rename from src/app/tab3/tab3.page.spec.ts rename to src/app/pages/login/login.page.spec.ts index a03da99..35b731b 100644 --- a/src/app/tab3/tab3.page.spec.ts +++ b/src/app/pages/login/login.page.spec.ts @@ -1,13 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { LoginPage } from './login.page'; -import { Tab3Page } from './tab3.page'; +describe('LoginPage', () => { + let component: LoginPage; + let fixture: ComponentFixture; -describe('Tab3Page', () => { - let component: Tab3Page; - let fixture: ComponentFixture; - - beforeEach(async () => { - fixture = TestBed.createComponent(Tab3Page); + beforeEach(() => { + fixture = TestBed.createComponent(LoginPage); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/pages/login/login.page.ts b/src/app/pages/login/login.page.ts new file mode 100644 index 0000000..0a3a1b1 --- /dev/null +++ b/src/app/pages/login/login.page.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterModule } from '@angular/router'; +import { IonContent, IonHeader, IonTitle, IonToolbar, IonGrid, IonRow, IonCol, IonItem, IonLabel, IonInput, IonButton } from '@ionic/angular/standalone'; +import { AuthService } from '../../services/auth.service'; +import { addIcons } from 'ionicons'; + +@Component({ + selector: 'app-login', + templateUrl: './login.page.html', + styleUrls: ['./login.page.scss'], + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonGrid, IonRow, IonCol, IonItem, IonLabel, IonInput, IonButton, CommonModule, FormsModule, RouterModule] +}) +export class LoginPage { + email: string = ''; + password: string = ''; + + constructor(private authService: AuthService, private router: Router) { + addIcons({ }); + } + + login() { + if (this.authService.login(this.email, this.password)) { + this.router.navigateByUrl('/profile'); + } + } +} diff --git a/src/app/pages/profile/profile.page.html b/src/app/pages/profile/profile.page.html new file mode 100644 index 0000000..923cb24 --- /dev/null +++ b/src/app/pages/profile/profile.page.html @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + + +
{{ user?.following || 0 }}
+
关注
+
+ +
{{ user?.followers || 0 }}
+
粉丝
+
+ +
{{ user?.likesCollections || 0 }}
+
获赞与收藏
+
+
+
+ + +
+ +
+
+ + + +
分享瞬间
+
+
+ + +
+ 编辑资料 + + + +
+
+ + + + +

笔记

+
+ +

收藏

+
+ +

赞过

+
+
+ + +
+ + + + +
+
+ Post Image + +
+
+
+ + +
+
+ Post Image + +
+
+
+
+
+
+ +
+ + +
+

请登录以查看您的个人资料。

+ 登录 + 注册 +
+
+
diff --git a/src/app/pages/profile/profile.page.scss b/src/app/pages/profile/profile.page.scss new file mode 100644 index 0000000..3992b94 --- /dev/null +++ b/src/app/pages/profile/profile.page.scss @@ -0,0 +1,288 @@ +// 确保 ion-header 和 ion-content 的背景透明,以便背景图显示 +ion-header { + --background: transparent; + --border-width: 0; // 移除默认边框 + position: absolute; // 让头部浮动在内容之上 + width: 100%; + z-index: 10; // 确保头部在最上层 +} + +ion-toolbar { + --background: transparent; + --border-width: 0; + color: white; // 头部图标和文字颜色 +} + +ion-button { + --color: white; // 头部按钮图标颜色 +} + +.profile-page-content { + --background: var(--ion-color-light); + --padding-top: 0; + --padding-bottom: 0; + --padding-start: 0; + --padding-end: 0; +} + +// 页面背景图容器 +.profile-background-image { + position: absolute; // 保持绝对定位,使其不影响文档流 + top: 0; + left: 0; + width: 100%; + height: 425px; // 调整背景图高度以覆盖更多区域 + overflow: hidden; // 隐藏超出容器的部分 + z-index: 1; // 确保在内容下方 +} + +// 内部的 img 标签样式 +.profile-background-image img { + width: 100%; + height: 100%; + object-fit: cover; // 关键:图片会覆盖整个容器,保持宽高比,裁剪多余部分 + object-position: center; // 关键:图片在容器中居中显示 + filter: brightness(0.8); // 调整亮度,让文字更清晰 + display: block; // 移除图片底部可能存在的空白 +} + +.profile-container { + position: relative; + z-index: 2; // 确保内容在背景图上方 + padding: 20px; + padding-top: 120px; // 增加顶部内边距,给头部和背景图留出更多空间 + color: white; // 默认文字颜色为白色 +} + +// 用户信息区 +.user-info-section { + display: flex; + align-items: center; + margin-bottom: 20px; +} + +.user-avatar { + width: 80px; + height: 80px; + margin-right: 15px; + border: 2px solid white; // 头像白色边框 + flex-shrink: 0; // 防止头像被压缩 +} + +.user-details { + flex-grow: 1; +} + +.user-name { + font-size: 1.8em; + font-weight: bold; + margin: 0; + color: white; +} + +.user-tags { + display: flex; + align-items: center; + margin-top: 5px; +} + +.gender-icon { + font-size: 1.2em; + margin-right: 5px; + &.female { + color: #ff69b4; // 粉色 + } + &.male { + color: #00bfff; // 蓝色 + } +} + +.verified-tag { + background-color: rgba(255, 255, 255, 0.3); + border-radius: 15px; + padding: 3px 8px; + font-size: 0.8em; + display: flex; + align-items: center; + color: white; + ion-icon { + margin-right: 3px; + font-size: 1em; + color: #4CAF50; // 绿色勾 + } +} + +.user-bio { + font-size: 0.9em; + color: rgba(255, 255, 255, 0.8); + margin-top: 10px; +} + +// 数据统计区 +.stats-grid { + margin-top: 20px; + margin-bottom: 20px; + text-align: center; +} + +.stat-item { + .stat-number { + font-size: 1.5em; + font-weight: bold; + color: white; + } + .stat-label { + font-size: 0.8em; + color: rgba(255, 255, 255, 0.7); + } +} + +// 组合后的操作区域 +.profile-actions-main-row { + display: flex; + justify-content: space-between; /* 将左右两部分推开 */ + align-items: flex-end; /* 底部对齐 */ + margin: 20px; + margin-top: -30px; /* 向上微调,使其与统计数据行对齐 */ + position: relative; /* 确保 z-index 作用 */ + z-index: 5; /* 确保在背景图和内容上方 */ +} + +.moment-section { + display: flex; + gap: 15px; /* 分享瞬间和我的瞬间之间的间距 (现在只有一个,但保留) */ +} + +.moment-action-item { + display: flex; + flex-direction: column; + align-items: center; + color: #ff69b4; /* 确保文字颜色可见 */ + background-color: transparent; + border-radius: 0; + padding: 0; + flex: none; +} + +.add-moment-button { + --background: transparent; + --color: white; + font-size: 2em; + height: auto; + width: auto; + border-radius: 0; + box-shadow: none; /* 移除阴影 */ + margin-bottom: 5px; /* 文字的间距 */ + --padding-start: 0; + --padding-end: 0; + --padding-top: 0; + --padding-bottom: 0; +} + +// 删除我的瞬间相关样式 +.moment-action-item.my-moments { + display: none; // 隐藏,或者直接删除这部分样式 +} + +.my-moments-thumbnail { + display: none; // 隐藏,或者直接删除这部分样式 +} + +.moment-action-label { + font-size: 0.8em; + color: white; + white-space: nowrap; /* 防止文字换行 */ +} + +.edit-settings-buttons { + display: flex; + gap: 10px; +} + +// 恢复编辑资料和设置按钮样式 +.edit-profile-button, .settings-button { + --background: rgba(255, 255, 255, 0.2); + --background-activated: rgba(255, 255, 255, 0.3); + --color: white; + --border-color: rgba(255, 255, 255, 0.5); + --border-width: 1px; + font-size: 0.9em; + height: 35px; + box-shadow: none; /* 移除阴影 */ + vertical-align: middle; +} + +.settings-button { + width: 35px; /* 使其成为正方形按钮 */ + --padding-start: 0; + --padding-end: 0; +} + +// 内容切换标签 +.profile-segment { + --background: white; /* segment 背景改为白色 */ + --background-checked: white; + --color: #999; /* 未选中文字颜色 */ + --color-checked: black; /* 选中文字颜色 */ + margin-top: 20px; + border-radius: 0; + padding: 0 20px; + height: 50px; + border-bottom: 1px solid #eee; /* 添加底部边框 */ +} + +ion-segment-button { + --indicator-color: black; // 下划线颜色 + --indicator-height: 2px; + --padding-start: 0; + --padding-end: 0; + --padding-top: 15px; + --padding-bottom: 15px; + font-size: 1.1em; + text-transform: none; // 防止大写 +} + +// 确保 segment 内部的文字颜色是黑色的 +.profile-segment ion-segment-button p { + color: black; +} + +.profile-segment ion-segment-button.segment-button-checked p { + font-weight: bold; // 选中时加粗 + color: black; +} + +// 内容展示区 (瀑布流布局) +.content-grid { + background-color: var(--ion-color-light); // 浅色背景,与顶部深色背景区分 + padding: 10px; + min-height: 400px; // 确保有足够高度 +} + +.post-item { + margin-bottom: 10px; // 帖子之间的间距 + background-color: white; /* 帖子背景改为白色 */ + border-radius: 8px; + overflow: hidden; // 确保图片圆角 + box-shadow: 0 2px 5px rgba(0,0,0,0.1); +} + +.post-image { + width: 100%; + height: auto; // 保持图片比例 + display: block; +} + +// 未登录状态 +.not-logged-in-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + text-align: center; + color: gray; + p { + margin-bottom: 20px; + } +} diff --git a/src/app/pages/profile/profile.page.spec.ts b/src/app/pages/profile/profile.page.spec.ts new file mode 100644 index 0000000..052dbe6 --- /dev/null +++ b/src/app/pages/profile/profile.page.spec.ts @@ -0,0 +1,17 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ProfilePage } from './profile.page'; + +describe('ProfilePage', () => { + let component: ProfilePage; + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = TestBed.createComponent(ProfilePage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/profile/profile.page.ts b/src/app/pages/profile/profile.page.ts new file mode 100644 index 0000000..8fc0a54 --- /dev/null +++ b/src/app/pages/profile/profile.page.ts @@ -0,0 +1,82 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterModule } from '@angular/router'; +import { + IonContent, IonHeader, IonTitle, IonToolbar, IonCard, IonCardHeader, IonCardTitle, + IonCardContent, IonItem, IonAvatar, IonLabel, IonButton, IonButtons, IonIcon, + IonGrid, IonRow, IonCol, IonSegment, IonSegmentButton // 引入新的组件 +} from '@ionic/angular/standalone'; +import { AuthService } from '../../services/auth.service'; +import { addIcons } from 'ionicons'; +// 导入所有需要的图标 +import { menuOutline, shareOutline, addOutline, settingsOutline, femaleOutline, checkmarkCircleOutline } from 'ionicons/icons'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.page.html', + styleUrls: ['./profile.page.scss'], + standalone: true, + imports: [ + IonContent, IonHeader, IonTitle, IonToolbar, IonCard, IonCardHeader, IonCardTitle, + IonCardContent, IonItem, IonAvatar, IonLabel, IonButton, IonButtons, IonIcon, + IonGrid, IonRow, IonCol, IonSegment, IonSegmentButton, // 添加到 imports + CommonModule, FormsModule, RouterModule + ] +}) +export class ProfilePage implements OnInit { + user: any; + selectedSegment: string = 'notes'; // 默认选中笔记 + + // 模拟帖子数据,用于瀑布流布局 + posts: any[] = [ + { segment: 'notes', imageUrl: 'assets/images/test/9.png', title: '我的第一篇笔记', likes: 10 }, + { segment: 'notes', imageUrl: 'assets/images/test/ar.png', title: '生活小记', likes: 25 }, + { segment: 'notes', imageUrl: 'assets/images/test/986.jpg', title: '旅行日记', likes: 8 }, + { segment: 'notes', imageUrl: 'assets/images/test/ar.png', title: '学习心得', likes: 15 }, + { segment: 'notes', imageUrl: 'assets/images/test/9.png', title: '美食分享', likes: 30 }, + { segment: 'collections', imageUrl: 'assets/images/test/986.jpg', title: '收藏夹1', likes: 5 }, + { segment: 'collections', imageUrl: 'assets/images/test/ar.png', title: '灵感收集', likes: 12 }, + { segment: 'liked', imageUrl: 'assets/images/test/ar.png', title: '赞过的作品', likes: 50 }, + { segment: 'liked', imageUrl: 'assets/images/test/986.jpg', title: '精彩瞬间', likes: 40 }, + ]; + + constructor(public authService: AuthService, private router: Router) { + // 添加所有需要的图标 + addIcons({ + menuOutline, + shareOutline, + addOutline, + settingsOutline, + femaleOutline, + checkmarkCircleOutline + }); + } + + ngOnInit() { + this.authService.user$.subscribe(user => { + // 模拟用户数据,包含更多字段以填充 UI + this.user = { + ...user, // 合并现有用户数据 + name: user?.name || '测试用户', + bio: user?.bio || '这个人很懒,什么都没留下。', + gender: user?.gender || 'female', // 模拟性别,可以是 'male' 或 'female' + isVerified: user?.isVerified || true, // 模拟是否认证 + following: user?.following || 11, // 关注数 + followers: user?.followers || 49, // 粉丝数 + likesCollections: user?.likesCollections || 362, // 获赞与收藏数 + // 已删除:myMomentsThumbnail + }; + }); + } + + logout() { + this.authService.logout(); + this.router.navigateByUrl('/login'); // 或者 '/tabs/profile' 根据你的路由设置 + } + + // 根据选中的 segment 过滤帖子数据 + getPostsBySegment(): any[] { + return this.posts.filter(post => post.segment === this.selectedSegment); + } +} diff --git a/src/app/pages/register/register.page.html b/src/app/pages/register/register.page.html new file mode 100644 index 0000000..91cb422 --- /dev/null +++ b/src/app/pages/register/register.page.html @@ -0,0 +1,23 @@ + + + 注册 + + + + + + + + 邮箱 + + + + 密码 + + + 注册 + 去登录 + + + + diff --git a/src/app/pages/register/register.page.scss b/src/app/pages/register/register.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/register/register.page.spec.ts b/src/app/pages/register/register.page.spec.ts new file mode 100644 index 0000000..3d6c25a --- /dev/null +++ b/src/app/pages/register/register.page.spec.ts @@ -0,0 +1,17 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RegisterPage } from './register.page'; + +describe('RegisterPage', () => { + let component: RegisterPage; + let fixture: ComponentFixture; + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/register/register.page.ts b/src/app/pages/register/register.page.ts new file mode 100644 index 0000000..1d0627b --- /dev/null +++ b/src/app/pages/register/register.page.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterModule } from '@angular/router'; +import { IonContent, IonHeader, IonTitle, IonToolbar, IonGrid, IonRow, IonCol, IonItem, IonLabel, IonInput, IonButton } from '@ionic/angular/standalone'; +import { AuthService } from '../../services/auth.service'; +import { addIcons } from 'ionicons'; + +@Component({ + selector: 'app-register', + templateUrl: './register.page.html', + styleUrls: ['./register.page.scss'], + standalone: true, + imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonGrid, IonRow, IonCol, IonItem, IonLabel, IonInput, IonButton, CommonModule, FormsModule, RouterModule] +}) +export class RegisterPage { + email: string = ''; + password: string = ''; + + constructor(private authService: AuthService, private router: Router) { + addIcons({ }); + } + + register() { + if (this.authService.login(this.email, this.password)) { + this.router.navigateByUrl('/profile'); + } + } +} diff --git a/src/app/services/auth.service.spec.ts b/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000..f1251ca --- /dev/null +++ b/src/app/services/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..4191cc1 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private userSubject = new BehaviorSubject(null); + user$ = this.userSubject.asObservable(); + + constructor() { + const storedUser = localStorage.getItem('user'); + if (storedUser) { + this.userSubject.next(JSON.parse(storedUser)); + } + } + + login(email: string, password: string): boolean { + // 模拟登录逻辑 + const user = { email, name: '测试用户', avatar: '' }; + localStorage.setItem('user', JSON.stringify(user)); + this.userSubject.next(user); + return true; + } + + logout() { + localStorage.removeItem('user'); + this.userSubject.next(null); + } + + isLoggedIn(): boolean { + return !!this.userSubject.value; + } +} diff --git a/src/app/services/feed.service.spec.ts b/src/app/services/feed.service.spec.ts new file mode 100644 index 0000000..656b960 --- /dev/null +++ b/src/app/services/feed.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { FeedService } from './feed.service'; + +describe('FeedService', () => { + let service: FeedService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(FeedService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/feed.service.ts b/src/app/services/feed.service.ts new file mode 100644 index 0000000..f2a6535 --- /dev/null +++ b/src/app/services/feed.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import moment from 'moment'; +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class FeedService { + private apiUrl = environment.apiUrl; + private authToken = environment.authToken; + + constructor(private http: HttpClient) {} + + fetchPosts(isTeam: boolean, showAll: boolean): Observable { + const url = `${this.apiUrl}?isweb=true&isteam=${isTeam}&showall=${showAll}`; + return this.http.get(url, { + headers: { + 'Authorization': this.authToken, + 'Content-Type': 'application/json' + } + }).pipe( + map(results => { + let rank = 0; + return results.map(result => { + let title = ''; + const strs = result.includes('===') ? result.split('===') : result.split('=='); + + if (strs.length > 1) { + const content = strs[1].trim(); + if (content.startsWith('Round')) { + title = `第 ${content.replace('Round', '').trim()} 回合`; + } else if (content.startsWith('终局审判')) { + title = '终局审判'; + } else if (content.startsWith('排名')) { + title = '排名'; + } else if (content.startsWith('伤害排行榜')) { + title = '伤害排行榜'; + } else if (content.startsWith('空投')) { + title = '空投'; + } else if (content.startsWith('技术得分排行榜')) { + title = '技术得分排行榜'; + } else if (content.startsWith('本场比赛最佳角色')) { + title = '本场比赛最佳角色'; + } else if (content.startsWith('团队模式随机分组')) { + title = '团队模式随机分组'; + } else if (content.startsWith('角色')) { + rank++; + title = `第 ${rank} 名:${content.replace('角色', '').trim()}`; + } + } else if (strs.length > 3) { + title = `${strs[1].trim()} ${strs[3].replace('角色', '').trim()}`; + } + + return { + author: 'FunGame 模拟', + title: title || '', + date: moment().format('YYYY-MM-DD HH:mm:ss'), + content: result, + likes: 999, + forwards: 999, + comments: 233 + }; + }); + }), + catchError(error => { + console.error('Fetch error:', error); + return throwError(() => new Error('Failed to fetch posts')); + }) + ); + } +} diff --git a/src/app/tab1/tab1.page.html b/src/app/tab1/tab1.page.html deleted file mode 100644 index 22e11e4..0000000 --- a/src/app/tab1/tab1.page.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Tab 1 - - - - - - - - Tab 1 - - - - - diff --git a/src/app/tab1/tab1.page.ts b/src/app/tab1/tab1.page.ts deleted file mode 100644 index 2c0d1ee..0000000 --- a/src/app/tab1/tab1.page.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component } from '@angular/core'; -import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; -import { ExploreContainerComponent } from '../explore-container/explore-container.component'; - -@Component({ - selector: 'app-tab1', - templateUrl: 'tab1.page.html', - styleUrls: ['tab1.page.scss'], - imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], -}) -export class Tab1Page { - constructor() {} -} diff --git a/src/app/tab2/tab2.page.html b/src/app/tab2/tab2.page.html deleted file mode 100644 index 38b153e..0000000 --- a/src/app/tab2/tab2.page.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Tab 2 - - - - - - - - Tab 2 - - - - - diff --git a/src/app/tab2/tab2.page.ts b/src/app/tab2/tab2.page.ts deleted file mode 100644 index 559aad5..0000000 --- a/src/app/tab2/tab2.page.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from '@angular/core'; -import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; -import { ExploreContainerComponent } from '../explore-container/explore-container.component'; - -@Component({ - selector: 'app-tab2', - templateUrl: 'tab2.page.html', - styleUrls: ['tab2.page.scss'], - imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent] -}) -export class Tab2Page { - - constructor() {} - -} diff --git a/src/app/tab3/tab3.page.html b/src/app/tab3/tab3.page.html deleted file mode 100644 index 222333d..0000000 --- a/src/app/tab3/tab3.page.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Tab 3 - - - - - - - - Tab 3 - - - - - diff --git a/src/app/tab3/tab3.page.ts b/src/app/tab3/tab3.page.ts deleted file mode 100644 index 5f6b0f1..0000000 --- a/src/app/tab3/tab3.page.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component } from '@angular/core'; -import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone'; -import { ExploreContainerComponent } from '../explore-container/explore-container.component'; - -@Component({ - selector: 'app-tab3', - templateUrl: 'tab3.page.html', - styleUrls: ['tab3.page.scss'], - imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], -}) -export class Tab3Page { - constructor() {} -} diff --git a/src/app/tabs/tabs.page.html b/src/app/tabs/tabs.page.html index 0f38384..2cc8a60 100644 --- a/src/app/tabs/tabs.page.html +++ b/src/app/tabs/tabs.page.html @@ -1,18 +1,18 @@ - - - Tab 1 + + + 首页 - - - Tab 2 + + + 动态 - - - Tab 3 + + + 我的 diff --git a/src/app/tabs/tabs.page.ts b/src/app/tabs/tabs.page.ts index d057eac..177b45a 100644 --- a/src/app/tabs/tabs.page.ts +++ b/src/app/tabs/tabs.page.ts @@ -1,7 +1,7 @@ import { Component, EnvironmentInjector, inject } from '@angular/core'; import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; -import { triangle, ellipse, square } from 'ionicons/icons'; +import { homeOutline, chatbubblesOutline, personOutline } from 'ionicons/icons'; @Component({ selector: 'app-tabs', @@ -13,6 +13,6 @@ export class TabsPage { public environmentInjector = inject(EnvironmentInjector); constructor() { - addIcons({ triangle, ellipse, square }); + addIcons({ homeOutline, chatbubblesOutline, personOutline }); } } diff --git a/src/app/tabs/tabs.routes.ts b/src/app/tabs/tabs.routes.ts deleted file mode 100644 index 15ba9e5..0000000 --- a/src/app/tabs/tabs.routes.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Routes } from '@angular/router'; -import { TabsPage } from './tabs.page'; - -export const routes: Routes = [ - { - path: 'tabs', - component: TabsPage, - children: [ - { - path: 'tab1', - loadComponent: () => - import('../tab1/tab1.page').then((m) => m.Tab1Page), - }, - { - path: 'tab2', - loadComponent: () => - import('../tab2/tab2.page').then((m) => m.Tab2Page), - }, - { - path: 'tab3', - loadComponent: () => - import('../tab3/tab3.page').then((m) => m.Tab3Page), - }, - { - path: '', - redirectTo: '/tabs/tab1', - pathMatch: 'full', - }, - ], - }, - { - path: '', - redirectTo: '/tabs/tab1', - pathMatch: 'full', - }, -]; diff --git a/src/assets/images/bg.jpg b/src/assets/images/bg.jpg new file mode 100644 index 0000000..59a51ab Binary files /dev/null and b/src/assets/images/bg.jpg differ diff --git a/src/assets/images/input.png b/src/assets/images/input.png new file mode 100644 index 0000000..60f16f1 Binary files /dev/null and b/src/assets/images/input.png differ diff --git a/src/assets/images/noavar.gif b/src/assets/images/noavar.gif new file mode 100644 index 0000000..a4a195b Binary files /dev/null and b/src/assets/images/noavar.gif differ diff --git a/src/assets/images/test/9.png b/src/assets/images/test/9.png new file mode 100644 index 0000000..24aacf0 Binary files /dev/null and b/src/assets/images/test/9.png differ diff --git a/src/assets/images/test/986.jpg b/src/assets/images/test/986.jpg new file mode 100644 index 0000000..d2bf046 Binary files /dev/null and b/src/assets/images/test/986.jpg differ diff --git a/src/assets/images/test/ar.png b/src/assets/images/test/ar.png new file mode 100644 index 0000000..cba4bab Binary files /dev/null and b/src/assets/images/test/ar.png differ diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3612073..6691f7a 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,3 +1,5 @@ export const environment = { - production: true + production: true, + apiUrl: 'https://api.redbud.fun/fungame/test', + authToken: 'Bearer askjrf2139ryf9' }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f56ff47..dd62acb 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,9 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + apiUrl: 'https://api.redbud.fun/fungame/test', + authToken: 'Bearer askjrf2139ryf9' }; /* diff --git a/src/main.ts b/src/main.ts index db355ec..6913f83 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,11 +4,13 @@ import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalo import { routes } from './app/app.routes'; import { AppComponent } from './app/app.component'; +import { provideHttpClient, withFetch } from '@angular/common/http'; bootstrapApplication(AppComponent, { providers: [ { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, provideIonicAngular(), provideRouter(routes, withPreloading(PreloadAllModules)), + provideHttpClient(withFetch()) ], });