瀏覽代碼

feat:"增加许多的东西"

15179169528 3 月之前
父節點
當前提交
5a41c06c59
共有 43 個文件被更改,包括 2408 次插入326 次删除
  1. 3 3
      poem-life-app/src/app/api.service.ts
  2. 6 1
      poem-life-app/src/app/app.routes.ts
  3. 7 5
      poem-life-app/src/app/book-management/book-management.page.html
  4. 29 22
      poem-life-app/src/app/book-management/book-management.page.ts
  5. 48 0
      poem-life-app/src/app/login/login.component.html
  6. 228 0
      poem-life-app/src/app/login/login.component.scss
  7. 22 0
      poem-life-app/src/app/login/login.component.spec.ts
  8. 83 0
      poem-life-app/src/app/login/login.component.ts
  9. 15 8
      poem-life-app/src/app/page-create/page-create.component.html
  10. 29 38
      poem-life-app/src/app/page-create/page-create.component.ts
  11. 8 7
      poem-life-app/src/app/page-createpic/page-createpic.component.html
  12. 18 13
      poem-life-app/src/app/page-createpic/page-createpic.component.ts
  13. 27 4
      poem-life-app/src/app/poem-detail/poem-detail.page.html
  14. 30 0
      poem-life-app/src/app/poem-detail/poem-detail.page.scss
  15. 106 27
      poem-life-app/src/app/poem-detail/poem-detail.page.ts
  16. 26 4
      poem-life-app/src/app/tab2/tab2.page.html
  17. 5 3
      poem-life-app/src/app/tab2/tab2.page.scss
  18. 206 63
      poem-life-app/src/app/tab2/tab2.page.ts
  19. 140 48
      poem-life-app/src/app/tab3/tab3.page.html
  20. 143 20
      poem-life-app/src/app/tab3/tab3.page.ts
  21. 23 9
      poem-life-app/src/app/tab4/tab4.page.html
  22. 52 0
      poem-life-app/src/app/tab4/tab4.page.scss
  23. 17 15
      poem-life-app/src/app/tab4/tab4.page.ts
  24. 195 0
      poem-life-app/src/app/tab5/tab5.component.html
  25. 178 0
      poem-life-app/src/app/tab5/tab5.component.scss
  26. 22 0
      poem-life-app/src/app/tab5/tab5.component.spec.ts
  27. 91 0
      poem-life-app/src/app/tab5/tab5.component.ts
  28. 10 0
      poem-life-app/src/app/tabs/tabs.routes.ts
  29. 二進制
      poem-life-app/src/assets/image/05/huihua.jpg
  30. 二進制
      poem-life-app/src/assets/image/05/shipian.jpg
  31. 二進制
      poem-life-app/src/assets/image/06/beijing.png
  32. 二進制
      poem-life-app/src/assets/image/06/login.jpg
  33. 3 0
      poem-life-app/src/global.scss
  34. 320 0
      poem-life-app/src/lib/ncloud.ts
  35. 29 0
      poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.html
  36. 0 0
      poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss
  37. 22 0
      poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.spec.ts
  38. 73 0
      poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts
  39. 36 0
      poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.html
  40. 0 0
      poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.scss
  41. 22 0
      poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts
  42. 94 0
      poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.ts
  43. 42 36
      poem-life-serve/db/dbServe.js

+ 3 - 3
poem-life-app/src/app/api.service.ts

@@ -27,7 +27,7 @@ export class ApiService {
   }
 
   // 获取消息
-  getMessage() {
-    return this.http.get("http://localhost:3000/api/message");
-  }
+  // getMessage() {
+  //   return this.http.get("http://localhost:3000/api/message");
+  // }
 }

+ 6 - 1
poem-life-app/src/app/app.routes.ts

@@ -25,6 +25,7 @@ export const routes: Routes = [
     path: 'card1',
     loadComponent: () => import('./tab1/card1/card1.page').then(m => m.Card1Page)
   },
+  
   {
     path: 'book-management/:id',  // 添加动态参数 :id
     loadComponent: () => import('./book-management/book-management.page').then(m => m.BookManagementPage)
@@ -32,5 +33,9 @@ export const routes: Routes = [
   {
     path: 'poem-detail/:id',  // 添加动态参数 :id
     loadComponent: () => import('./poem-detail/poem-detail.page').then(m => m.PoemDetailPage)
-  }
+  },
+  // {
+  //   path: 'login',  // 添加 login 页面路径
+  //   loadComponent: () => import('./login/login.component').then(m => m.LoginComponent)
+  // },
 ];

+ 7 - 5
poem-life-app/src/app/book-management/book-management.page.html

@@ -1,14 +1,16 @@
 <ion-header>
   <ion-toolbar>
-    <ion-title>书籍内容</ion-title>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/tab2"></ion-back-button>
+    </ion-buttons>
+    <ion-title>书籍管理</ion-title>
   </ion-toolbar>
 </ion-header>
 
 <ion-content>
   <ion-list>
-    <ion-item *ngFor="let poem of poems" (click)="goToPoemDetail(poem.id)">
-      {{ poem.poem_title }}
+    <ion-item button *ngFor="let poem of poems" (click)="goToPoemDetail(poem.id)">
+      {{ poem.title }}
     </ion-item>
   </ion-list>
-</ion-content>
-
+</ion-content>

+ 29 - 22
poem-life-app/src/app/book-management/book-management.page.ts

@@ -1,41 +1,48 @@
-import { IonList, IonItem } from '@ionic/angular/standalone';
 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 { ActivatedRoute, Router } from '@angular/router';
-import { ApiService } from '../api.service';
+import { HttpClient } from '@angular/common/http';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from '@ionic/angular';
 
 @Component({
   selector: 'app-book-management',
   templateUrl: './book-management.page.html',
   styleUrls: ['./book-management.page.scss'],
   standalone: true,
-  imports: [IonContent, IonHeader, IonTitle, IonToolbar,IonList,IonItem, CommonModule, FormsModule]
+  imports: [CommonModule, IonicModule]
 })
 export class BookManagementPage implements OnInit {
+  bookId: number | null = null; // 初始化为 null
+  poems: any[] = []; // 用于存储诗词数据
+  private baseUrl = 'http://localhost:3000/api'; // 后端 API 基础 URL
 
-  bookId: number | null = null;  // 给 bookId 一个默认值
-  poems: any[] = [];
-
-  constructor(private route: ActivatedRoute, private apiService: ApiService,private router: Router) {}
+  constructor(private route: ActivatedRoute, private http: HttpClient, private router: Router) {}
 
   ngOnInit() {
-    const bookIdFromRoute = this.route.snapshot.paramMap.get('id');
-    if (bookIdFromRoute) {
-      this.bookId = +bookIdFromRoute;  // 使用 + 转换为数字
-    }
+    // 订阅路由参数,以获取传递的书籍ID
+    this.route.params.subscribe(params => {
+      this.bookId = +params['id']; // 获取传递的书籍ID
+      if (this.bookId) {
+        this.getPoemsByBook(this.bookId); // 获取该书籍下的诗词
+      }
+    });
+  }
 
-    // 获取该书籍的所有诗
-    if(this.bookId !== null)
-    this.apiService.getPoems(this.bookId).subscribe(data => {
-      this.poems = data;
+  // 获取指定书籍下的诗词
+  getPoemsByBook(bookId: number) {
+    this.http.get<any[]>(`${this.baseUrl}/poems/${bookId}`).subscribe(data => {
+      this.poems = data; // 保存诗词数据
     }, error => {
-      console.error('Error fetching poems:', error);
+      console.error('Error fetching poems', error);
     });
   }
+
+  // 导航到诗词详情页面
   goToPoemDetail(poemId: number) {
-    this.router.navigate(['/poem-detail', poemId]);
+    if (this.bookId !== null) {
+      this.router.navigate(['../poem-detail', poemId], { queryParams: { bookId: this.bookId } }); // 传递书籍ID作为查询参数
+    } else {
+      console.error('Book ID is not set');
+    }
   }
-}
-
+}

+ 48 - 0
poem-life-app/src/app/login/login.component.html

@@ -0,0 +1,48 @@
+<!-- src/app/login/login.component.html -->
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/tab3"></ion-back-button>
+    </ion-buttons>
+    <ion-title>登录注册</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<div class="ion-padding">
+
+  <div id="login_card">
+    <h1>诗创坊</h1>
+    @if(!currentUser?.id){
+      <div class=".custom-button" (click)="login()">
+          <h4>登录</h4>
+        </div>
+        <div class=".custom-button" (click)="signup()">
+          <h4>注册</h4>
+        </div>
+    }
+    @if(currentUser?.id){
+      <div class="lower">您已登录</div>
+    }
+    <div class=".custom-button" (click)="logout()">
+      <h4>登出</h4>
+    </div>
+  </div>
+
+  
+
+  <!-- 登录表单 -->
+  <form *ngIf="showLoginForm" (ngSubmit)="onSubmit()">
+    <ion-item lines="none">
+      <ion-label position="floating" color="primary">请输入用户名</ion-label>
+      <ion-input [(ngModel)]="username" name="username" type="text"></ion-input>
+    </ion-item>
+    
+    <ion-item lines="none">
+      <ion-label position="floating" color="primary">请输入密码</ion-label>
+      <ion-input [(ngModel)]="password" name="password" type="password"></ion-input>
+    </ion-item>
+    
+    <ion-button expand="block" shape="round" type="submit" class="login-button">登录</ion-button>
+  </form>
+
+</div>

+ 228 - 0
poem-life-app/src/app/login/login.component.scss

@@ -0,0 +1,228 @@
+/* src/app/login/login.component.scss */
+.bg-color {
+    --background: #f4f4f4;  /* 设置背景颜色 */
+  }
+  
+  .top-list {
+    margin-top: 10px;
+  }
+  
+  ion-item {
+    --inner-padding-end: 10px;
+  }
+  
+  ion-avatar {
+    width: 40px;
+    height: 40px;
+  }
+  
+  ion-label h1 {
+    font-size: 60px;
+    font-weight: bold;
+  }
+  
+  ion-label p {
+    font-size: 14px;
+    color: #888;
+  }
+  
+  ion-button {
+    margin-top: 20px;
+  }
+  
+  .box {
+    width: 100%;
+    margin: auto;
+  }
+  
+  .top {
+    position: relative;
+    padding-top: 30px; /* 使用 px 替代 rpx */
+    width: 100%;
+    height: 300px; /* 使用 px 替代 rpx */
+  }
+  
+  .top:after {
+    width: 140%;
+    height: 300px;
+    position: absolute;
+    left: -20%;
+    top: 0;
+    z-index: -1;
+    content: '';
+    border-radius: 0 0 60% 60%;
+    background-color: #426db5;
+  }
+  
+  .region {
+    margin: auto;
+    width: 450px;
+  }
+  
+  .upper {
+    display: flex; /* 使用弹性盒布局,使子元素能够灵活排列 */
+    justify-content: center; /* 在主轴上居中对齐子元素 */
+    align-items: center; /* 在交叉轴上居中对齐子元素 */
+  }
+  
+  .img-box {
+    width: 120px;
+    height: 120px; 
+    border-radius: 50%; /* 设置边框半径为 50%,使盒子变为圆形 */
+    overflow: hidden; /* 确保任何超出部分都被隐藏 */
+  }
+  
+  .img-box img {
+    width: 100%;
+    height: 100%;
+  }
+  
+  .info {
+    padding-left: 30px; /* 设置左侧内边距为 30 像素 */
+    line-height: 55px; /* 设置行高为 55 像素,增加文本行之间的垂直间距 */
+    flex-direction: column; /* 将子元素排列为纵向(列)方向 */
+  }
+  
+  .name {
+    font-size: 20px; /* 设置字体大小为 40 像素 */
+    letter-spacing: 5px; /* 设置字母间距为 5 像素 */
+    color: #FFFFFF; /* 设置字体颜色为白色 */
+  }
+  
+  .no {
+    font-size: 18px; /* 设置字体大小为 24 像素 */
+    letter-spacing: 2px; /* 设置字母间距为 2 像素 */
+    color: #b3dffe; /* 设置字体颜色为淡蓝色 */
+  }
+  
+  .lower {
+    display: flex;
+    justify-content: center;
+    line-height: 100px; 
+    font-size: 20px; 
+    color: #6d0cec;
+  }
+  
+  .lower1 {
+    width: 200px;
+    height: 10px;
+    display: flex;
+    margin: auto;
+    flex-direction: column; /* 设置为垂直方向 */
+  }
+  
+  .custom-button {
+    color: #ffffff; /* 按钮文字颜色 */
+    border: none;
+    font-weight: bold;
+    padding: 15px 20px; /* 添加内边距 */
+    border-radius: 10px; /* 圆角边框 */
+    text-align: center; /* 文本居中 */
+    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); /* 透明阴影 */
+  }
+  
+  .count {
+    display: flex; /* 使用弹性盒布局,使子元素能够灵活排列 */
+    margin: auto;
+    width: 200px; /* 设置元素的宽度为 350 像素 */
+    line-height: 16px; /* 设置行高为 80 像素,增加文本行之间的垂直间距 */
+    border-radius: 50px; /* 设置边框半径为 50 像素,使元素的角变圆润 */
+    background-color: #426db5;
+  }
+  
+  .title {
+    width: auto;
+    display: flex; /* 将元素设置为弹性盒布局,使子元素能够灵活排列 */
+    justify-content: center; /* 在主轴上居中对齐子元素 */
+    align-items: center; /* 在交叉轴上居中对齐子元素 */
+  }
+  
+  .title span {
+    font-size: 16px; 
+    padding-left: 15px; 
+    color: #b3dffe;
+  }
+  
+  .icon-container {
+    margin-top: 10px;
+    display: flex; /* 使用弹性盒布局 */
+    justify-content: space-around; /* 在主轴上均匀分配空间 */
+  }
+  
+  .icon-item {
+    text-align: center; /* 使文本居中对齐 */
+  }
+  
+  .icon-item ion-icon {
+    font-size: 25px; /* 设置图标大小 */
+    margin-bottom: 10px; /* 图标与文本之间的间距 */
+  }
+  
+  .icon-item span {
+    display: block; /* 将文本设置为块级元素,以便于控制布局 */
+    font-size: 16px; /* 设置文本大小 */
+    color: #333; /* 设置文本颜色 */
+  }
+  
+  #login_card {
+    width: 90%;
+    max-width: 400px;
+    background-color: rgba(255, 255, 255, 0.8); /* 透明背景 */
+    margin: auto;
+    margin-top: 20%;
+    text-align: center;
+    border-radius: 10px;
+    padding: 50px 50px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
+  
+    h2 {
+      color: #330867;
+      margin-bottom: 30px;
+    }
+  
+    .auth-button {
+      margin-bottom: 20px;
+      background-image: linear-gradient(to right, #30cfd0, #330867); /* 渐变颜色 */
+      border: none;
+      color: white;
+      font-weight: bold;
+  
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+  }
+  
+  .login-button {
+    margin-top: 20px;
+    background-image: linear-gradient(to right, #ff7e5f, #feb47b); /* 彩色渐变 */
+    border: none;
+    color: white;
+    font-weight: bold;
+    padding: 10px 20px; /* 添加一些内边距 */
+    border-radius: 5px; /* 圆角边框 */
+    cursor: pointer; /* 改变鼠标悬停时的光标样式 */
+
+    &:hover {
+        opacity: 0.2;
+    }
+}
+  
+.ion-padding {
+  background-image: url('../../assets/image/06/login.jpg'); /* 确保路径正确 */
+  background-size: cover; /* 背景图覆盖整个区域 */
+  background-position: center; /* 背景图居中 */
+  background-repeat: no-repeat; /* 确保背景图不重复 */
+  position: fixed; /* 固定背景图,使其覆盖整个视口 */
+  top: 0;
+  left: 0;
+  width: 100%; /* 使用视口宽度 */
+  height: 100vh; /* 使用视口高度 */
+  z-index: -1; /* 将背景图置于最底层 */
+  image-rendering: pixelated; /* 其他浏览器 */
+  }
+  
+  
+  
+  
+  

+ 22 - 0
poem-life-app/src/app/login/login.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+  let component: LoginComponent;
+  let fixture: ComponentFixture<LoginComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [LoginComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(LoginComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 83 - 0
poem-life-app/src/app/login/login.component.ts

@@ -0,0 +1,83 @@
+// src/app/login/login.component.ts
+import { Component } from '@angular/core';
+import { Router } from '@angular/router'; // 导入 Router 模块
+import { IonicModule, IonContent, IonHeader, IonToolbar, IonButtons, IonMenuButton, IonButton, IonIcon, IonLabel, IonInput, IonItem,  } from '@ionic/angular';
+import { FormsModule } from '@angular/forms'; // 导入 FormsModule
+import { CloudUser } from 'src/lib/ncloud';
+import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.component';
+import { addIcons } from 'ionicons';
+import { folderOutline, timeOutline, starOutline, heartOutline, chevronForwardOutline, personOutline, shieldOutline, callOutline, lockClosedOutline } from 'ionicons/icons';
+import { ModalController } from '@ionic/angular/standalone';
+import { AlertController } from '@ionic/angular/standalone'; // 导入所需的模块
+
+@Component({
+  selector: 'app-login',
+  templateUrl: './login.component.html',
+  styleUrls: ['./login.component.scss'],
+  standalone: true,
+  imports: [IonicModule, FormsModule], // 确保导入 FormsModule
+})
+export class LoginComponent {
+  username: string = '';
+  password: string = '';
+  currentUser: CloudUser | undefined;
+  isRegistered: boolean = false;
+  showLoginForm: boolean = false;
+
+  constructor(
+    private router: Router,
+    private alertController: AlertController,
+    private modalCtrl: ModalController
+  ) {
+    addIcons({ folderOutline, timeOutline, starOutline, heartOutline, chevronForwardOutline, personOutline, shieldOutline, callOutline, lockClosedOutline });
+    this.currentUser = new CloudUser();
+  }
+
+  async onSubmit() {
+    // 处理登录逻辑
+    console.log('Username:', this.username);
+    console.log('Password:', this.password);
+
+    // 示例:成功登录后导航到主页或其他页面
+    if (this.username && this.password) {
+      // 这里可以添加实际的登录验证逻辑
+      this.currentUser = new CloudUser(); // 假设登录成功
+      this.router.navigate(['/tab3']); // 假设主页面是 tabs/tab3
+    }
+  }
+
+  async login() {
+    // 弹出登录窗口
+    let user = await openUserLoginModal(this.modalCtrl);
+    if (user?.id) {
+      this.currentUser = user;
+      this.showLoginForm = false; // 关闭登录表单
+      this.router.navigate(['/tabs/tab3']);
+    }
+  }
+
+  async signup() {
+    // 弹出注册窗口
+    let user = await openUserLoginModal(this.modalCtrl, "signup");
+    if (user?.id) {
+      this.isRegistered = true; // 设置注册状态为已注册
+      this.currentUser = user;
+      this.showLoginForm = false; // 关闭登录表单
+    }
+  }
+
+  logout() {
+    this.currentUser?.logout();
+    this.isRegistered = false; // 设置注册状态为未注册
+    this.showLoginForm = false; // 关闭登录表单
+  }
+
+ 
+
+ 
+
+  toggleLoginForm(show: boolean) {
+    this.showLoginForm = show;
+  }
+}

+ 15 - 8
poem-life-app/src/app/page-create/page-create.component.html

@@ -1,19 +1,26 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/tab4"></ion-back-button>
+    </ion-buttons>
+    <ion-title>{{ content.title }}</ion-title>
+  </ion-toolbar>
+</ion-header>
+
 <ion-content>
   <h1>形式(绝句,律诗,词曲)</h1>
   <ion-input [value]="style" (ionInput)="styleInput($event)"></ion-input>
-    <!-- 文本域:生成提示词 -->
+  
   <h1>内容风格描述</h1>
   <ion-textarea [value]="userPrompt" (ionInput)="promptInput($event)" placeholder="文本提示词" autoGrow="true"></ion-textarea>
 
-  <!-- 按钮:执行消息生成函数 -->
   <ion-button (click)="sendMessage()" expand="block">消息生成</ion-button>
 
-  <!-- 展示:返回消息内容 -->
-  @if(!isComplete){
+  <div *ngIf="!isComplete">
     <div>{{responseMsg}}</div>
-  }
+  </div>
 
-  @if(isComplete){
+  <div *ngIf="isComplete">
     <fm-markdown-preview class="content-style" [content]="responseMsg"></fm-markdown-preview>
-  }
-</ion-content>
+  </div>
+</ion-content>

+ 29 - 38
poem-life-app/src/app/page-create/page-create.component.ts

@@ -1,52 +1,46 @@
-/*
- * @Author: 危齐晟 1913361097@qq.com
- * @Date: 2024-12-01 20:33:19
- * @LastEditors: 危齐晟 1913361097@qq.com
- * @LastEditTime: 2024-12-15 20:36:01
- * @FilePath: \202226701045\poem-life-app\src\app\page-create\page-create.component.ts
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
-import { routes } from './../tabs/tabs.routes';
-import { ActivatedRoute, Router } from '@angular/router';
+import { Router } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
 import { Component, OnInit } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent,IonButton,IonTextarea,IonInput } from '@ionic/angular/standalone';
-/** 引用:从fmode-ng库引用FmodeChatCompletion类 */
-import { FmodeChatCompletion,MarkdownPreviewModule} from 'fmode-ng';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonButtons, IonIcon, IonBackButton } from '@ionic/angular/standalone';
+import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+import { CommonModule } from '@angular/common';
 
 @Component({
   selector: 'app-page-create',
   templateUrl: './page-create.component.html',
   styleUrls: ['./page-create.component.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton,
-    IonTextarea,IonInput,MarkdownPreviewModule
-    ],
+  imports: [IonBackButton, IonIcon, IonButtons, IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, MarkdownPreviewModule
+    ,CommonModule
+  ],
 })
-export class PageCreateComponent  implements OnInit {
+export class PageCreateComponent implements OnInit {
   isComplete: boolean = false;
-  ngOnInit(){}
+  ngOnInit() {}
 
-  content: {title: string} = {title: '诗创'};
-  constructor(private ActivatedRoute: ActivatedRoute,private router: Router) {}
-  goBack(){
-    this.router.navigateByUrl('../tab4.html');
-  }
-  // 用户输入提示词
-  style:string = "诗句格式"
-  styleInput(ev:any){
+  content: { title: string } = { title: '诗创' };
+  constructor(private ActivatedRoute: ActivatedRoute, private router: Router) {}
+
+  // goBack() {
+  //   this.router.navigate(['/tabs/tab4']); // 返回到 Tab4
+  // }
+
+  style: string = "诗句格式";
+  styleInput(ev: any) {
     this.style = ev.detail.value;
     this.isComplete = false;
     this.responseMsg = '';
   }
-   userPrompt:string = "请描述你的诗歌需求"
-  promptInput(ev:any){
+
+  userPrompt: string = "请描述你的诗歌需求";
+  promptInput(ev: any) {
     this.userPrompt = ev.detail.value;
     this.isComplete = false;
     this.responseMsg = '';
   }
-  // 属性:组件内用于展示消息内容的变量
-  responseMsg:any = ""
-  // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象。
+
+  responseMsg: any = "";
+
   sendMessage() {
     console.log("create");
 
@@ -71,14 +65,11 @@ export class PageCreateComponent  implements OnInit {
     ]);
 
     completion.sendCompletion().subscribe((message: any) => {
-      // 打印消息体
       console.log(message.content);
-      // 赋值消息内容给组件内属性
-      this.responseMsg = message.content
-      if(message?.complete){
-        this.isComplete = true
+      this.responseMsg = message.content;
+      if (message?.complete) {
+        this.isComplete = true;
       }
     });
   }
-
-}
+}

+ 8 - 7
poem-life-app/src/app/page-createpic/page-createpic.component.html

@@ -1,17 +1,18 @@
-<ion-header [translucent]="true">
-  <ion-toolbar color="primary">
-    <ion-title>
-      古诗云想 | 图片生成进度: {{imagineWork?.progress || 0}}%
-    </ion-title>
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/tab4"></ion-back-button>
+    </ion-buttons>
+    <ion-title>{{ content.title }}</ion-title>
   </ion-toolbar>
 </ion-header>
-
 <ion-content [fullscreen]="true" style="background-color: #f9f9f9; padding: 16px;">
   <!-- 生成提示词 -->
   <h2 style="text-align: center; color: #5a5a5a;">请输入您的古诗</h2>
   <ion-textarea
-    [value]="userPrompt"
+    
     (ionInput)="promptInput($event)"
+    (focus)="clearPrompt()" 
     placeholder="在此输入您想要的古诗..."
     autoGrow="true"
     style="background-color: white; color: black; border-radius: 8px; margin-bottom: 16px;"

+ 18 - 13
poem-life-app/src/app/page-createpic/page-createpic.component.ts

@@ -1,6 +1,6 @@
 import { CommonModule } from '@angular/common';
 import { Component, OnInit } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonBackButton } from '@ionic/angular/standalone';
 import { IonInput, IonTextarea, IonButton } from "@ionic/angular/standalone";
 import { DalleOptions, FmodeChatCompletion, ImagineWork, MarkdownPreviewModule } from 'fmode-ng';
 
@@ -9,27 +9,33 @@ import { DalleOptions, FmodeChatCompletion, ImagineWork, MarkdownPreviewModule }
   templateUrl: './page-createpic.component.html',
   styleUrls: ['./page-createpic.component.scss'],
   standalone: true,
-  imports: [
+  imports: [IonBackButton, IonButtons, 
     IonHeader, IonToolbar, IonTitle, IonContent,
     IonButton, IonInput,
-    IonTextarea, MarkdownPreviewModule,CommonModule
+    IonTextarea, MarkdownPreviewModule, CommonModule
   ],
 })
 export class PageCreatepicComponent implements OnInit {
 
   userPrompt: string = "云想衣裳花想容,春风拂槛露华浓。若非群玉山头见,会向瑶台月下逢";
-  promptInput(ev: any) {
-    this.userPrompt = ev.detail.value;
-  }
-
-  imagineWork: ImagineWork | undefined;
-  images: Array<string> = [];
   responseMsg: string | undefined; // 添加用于接收生成的消息内容
+  images: Array<string> = [];
+  imagineWork: ImagineWork | undefined;
 
   constructor() {
     this.imagineWork = new ImagineWork(); // 初始化 ImagineWork 实例
   }
 
+  ngOnInit() {}
+
+  promptInput(ev: any) {
+    this.userPrompt = ev.detail.value;
+  }
+
+  clearPrompt() {
+    this.userPrompt = ''; // 清空示例诗句内容
+  }
+
   async createImage() {
     let PromptTemplate = `
     请您作为一位专业的画家,分析以下古诗中的情感、意象和色彩。请考虑以下要素,并将其转化为视觉艺术的表现:
@@ -43,7 +49,6 @@ export class PageCreatepicComponent implements OnInit {
     ${this.userPrompt}
     `;
 
-
     let completion = new FmodeChatCompletion([
       { role: "system", content: "" },
       { role: "user", content: PromptTemplate }
@@ -53,7 +58,7 @@ export class PageCreatepicComponent implements OnInit {
       next: (message: any) => {
         console.log(message.content);
         this.responseMsg = message.content;
-        if (message?.complete && this.imagineWork) { // 确保 imagineWork 已初始化
+        if (message?.complete && this.imagineWork) {
           let options: DalleOptions = { prompt: this.userPrompt };
           this.imagineWork.draw(options).subscribe({
             next: (work) => {
@@ -76,5 +81,5 @@ export class PageCreatepicComponent implements OnInit {
     });
   }
 
-  ngOnInit() {}
-}
+  content: { title: string } = { title: '画创' };
+}

+ 27 - 4
poem-life-app/src/app/poem-detail/poem-detail.page.html

@@ -1,12 +1,35 @@
 <ion-header>
   <ion-toolbar>
-    <ion-title>诗歌详情</ion-title>
+    <ion-buttons slot="start">
+      <ion-back-button (click)="goBack()"></ion-back-button>
+    </ion-buttons>
+    <ion-title>{{ poemTitle || '诗词详情' }}</ion-title>
   </ion-toolbar>
 </ion-header>
 
+<!-- 背景图层 -->
+<div class="background"></div>
+
+<!-- 内容层 -->
 <ion-content>
-  <div *ngIf="poemContent">
-    <h2>{{ poemId }}</h2>
-    <p>{{ poemContent }}</p>
+  <div class="content-wrapper">
+    <div *ngIf="poemTitle !== null && poemContent !== null" class="poem-content">
+      <!-- <h1>{{ poemTitle }}</h1> -->
+      <pre>{{ poemContent }}</pre>
+    </div>
+    <div *ngIf="poemTitle === null || poemContent === null" class="loading-message">
+      <p>加载中...</p>
+    </div>
   </div>
 </ion-content>
+
+  <!-- <div *ngIf="poemTitle !== null">{{ poemTitle }}</div><br> -->
+  <!-- <div><pre>{{poemId}}</pre></div> -->
+  <!-- <div>{{title}}</div><br>
+  <p>aaaa</p><br>
+  <div *ngIf="poemContent !== null">
+    <pre>{{ poemContent }}</pre>
+  </div>
+  <div *ngIf="poemContent === null">
+    <p>加载中...</p>
+  </div> -->

+ 30 - 0
poem-life-app/src/app/poem-detail/poem-detail.page.scss

@@ -0,0 +1,30 @@
+.background {
+    background-image: url('../../assets/image/06/beijing.png'); /* 确保路径正确 */
+    background-size: cover; /* 背景图覆盖整个区域 */
+    background-position: center; /* 背景图居中 */
+    background-repeat: no-repeat; /* 确保背景图不重复 */
+    position: fixed; /* 固定背景图,使其覆盖整个视口 */
+    top: 0;
+    left: 0;
+    width: 100%; /* 使用视口宽度 */
+    height: 100vh; /* 使用视口高度 */
+    z-index: -1; /* 将背景图置于最底层 */
+}
+
+.content-wrapper {
+    
+    color: rgb(3, 3, 3); /* 设置文本颜色,确保在背景上可读 */
+    text-align: center; /* 文本居中对齐 */
+    align-items: center; // 垂直居中
+    padding: 20px; /* 添加一些内边距 */
+    max-width: 600px; /* 最大宽度 */
+    margin: auto; /* 水平居中内容 */
+    background-color: rgba(0, 0, 0, 0); /* 半透明黑色背景 */
+    border-radius: 10px; /* 圆角 */
+    position: relative; /* 确保内容在背景图之上 */
+    z-index: 1; /* 内容层位于背景图之上 */
+}
+
+ion-content {
+    --background: transparent; /* 确保 ion-content 背景透明 */
+}

+ 106 - 27
poem-life-app/src/app/poem-detail/poem-detail.page.ts

@@ -1,43 +1,122 @@
-/*
- * @Author: 危齐晟 1913361097@qq.com
- * @Date: 2024-12-17 14:21:20
- * @LastEditors: 危齐晟 1913361097@qq.com
- * @LastEditTime: 2024-12-17 14:48:09
- * @FilePath: \202226701045\poem-life-app\src\app\poem-detail\poem-detail.page.ts
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
-import { ApiService } from './../api.service';
 import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { HttpClient } from '@angular/common/http';
 import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
-import { ActivatedRoute } from '@angular/router';
+import { IonicModule } from '@ionic/angular';
+import { forkJoin } from 'rxjs';
+
+
 
 @Component({
   selector: 'app-poem-detail',
   templateUrl: './poem-detail.page.html',
   styleUrls: ['./poem-detail.page.scss'],
   standalone: true,
-  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]
+  imports: [CommonModule, IonicModule]
 })
 export class PoemDetailPage implements OnInit {
-  poemId: number | null = null;  // 给 poemId 一个默认值
-  poemContent: string = '';  // 给 poemContent 一个空字符串默认值
+  bookId: number | null = null; // 初始化为 null
+  poemId: number | null = null; // 初始化为 null
+  poemContent: string | null = null; // 初始化为 null
+  poemTitle: string | null = null;
+  title: string | null = null;
+  private baseUrl = 'http://localhost:3000/api'; // 后端 API 基础 URL
+
+  constructor(private route: ActivatedRoute, private http: HttpClient, private router: Router) {
+  }
 
-  constructor(private route: ActivatedRoute,private apiService:ApiService) { }
 
   ngOnInit() {
-    const poemIdFromRoute = this.route.snapshot.paramMap.get('id');
-    if (poemIdFromRoute) {
-      this.poemId = +poemIdFromRoute;  // 使用 + 转换为数字
+   // 订阅路由参数,以获取传递的诗词ID和书籍ID
+   this.route.params.subscribe(params => {
+    this.poemId = +params['id']; // 获取传递的诗词ID
+    if (this.poemId) {
+      // this.getPoemContent(this.poemId); // 获取诗词内容
+      // this.getPoemTitle(this.poemId);
+      this.loadPoemDetails(this.poemId);
     }
-    // 获取诗歌内容
-    if (this.poemId !== null) {
-      this.apiService.getPoemContent(this.poemId).subscribe(data => {
-        this.poemContent = data.content;
-      }, error => {
-        console.error('Error fetching poem content:', error);
-      });
+
+    // 获取传递的书籍ID(假设书籍ID通过其他方式传递)
+    const bookIdParam = this.route.snapshot.queryParamMap.get('bookId');
+    // const bookIdParam = params['bookId'];
+    if (bookIdParam) {
+      this.bookId = +bookIdParam;
+    }
+  });
+  }
+
+
+  loadPoemDetails(poemId: number) {
+    // 合并诗词标题和内容的请求
+    forkJoin([
+      this.http.get<{ id: number; book_id: number; author_id: number; genre_id: number; title: string }>(`${this.baseUrl}/poems/${poemId}`),
+      this.http.get<{ content: string }>(`${this.baseUrl}/poem-contents/${poemId}`)
+    ]).subscribe(
+      ([poemData, content]) => {
+        console.log('Received poem data:', poemData); // 添加日志
+        console.log('Received content:', content); // 添加日志
+        this.poemTitle = poemData.title ;
+        this.title = this.poemTitle; // 设置页面标题
+        this.poemContent = content.content || '加载失败或无内容';
+      },
+      error => {
+        console.error('Error fetching poem details', error);
+      }
+    );
+  }
+
+    // 返回逻辑
+    goBack() {
+      if (this.bookId !== null) {
+        this.router.navigate(['book-management', this.bookId]); // 导航到对应类型的书籍管理页面
+      } else {
+        console.error('Book ID is not set');
+      }
     }
   }
-}
+
+  
+  // loadPoemDetails(poemId: number) {
+  //   // 获取诗词标题
+  //   const id = poemId;
+  //   this.http.get<{ title: string }>(`${this.baseUrl}/poems/${id}`).subscribe(
+  //     data => {
+  //       this.poemTitle = data.title || '未知标题'; // 设置默认值以防为空
+  //       this.title = this.poemTitle; // 设置页面标题
+  //     },
+  //     error => {
+  //       console.error('Error fetching poem title', error);
+  //     }
+  //   );
+
+  //   // 获取诗词内容
+  //   this.http.get<{ content: string }>(`${this.baseUrl}/poem-contents/${poemId}`).subscribe(
+  //     data => {
+  //       this.poemContent = data.content || '加载失败或无内容'; // 设置默认值以防为空
+  //     },
+  //     error => {
+  //       console.error('Error fetching poem content', error);
+  //     }
+  //   );
+  // }
+
+
+  // getPoemTitle(poemId: number) {
+  //   // const id = poemId;
+  //   this.http.get<{ title: string }>(`${this.baseUrl}/poems/${poemId}`).subscribe(data => {
+  //     this.poemTitle = data.title; // 保存诗词标题
+  //   }, error => {
+  //     console.error('Error fetching poem title', error);
+  //   });
+  // }
+  // // 获取诗词内容
+  // getPoemContent(poemId: number) {
+  //   this.http.get<{ content: string }>(`${this.baseUrl}/poem-contents/${poemId}`).subscribe(data => {
+  //     this.poemContent = data.content; // 保存诗词内容
+  //   }, error => {
+  //     console.error('Error fetching poem content', error);
+  //   });
+  // }
+
+
+

+ 26 - 4
poem-life-app/src/app/tab2/tab2.page.html

@@ -1,4 +1,3 @@
-
 <ion-header [translucent]="true">
   <ion-toolbar>
     <ion-buttons slot="end">
@@ -26,8 +25,8 @@
     <ion-grid>
       <ion-col size="2" class="fixed-sidebar">
         <ion-list>
-          <ion-item button *ngFor="let category of categories" (click)="selectCategory(category)">
-            <ion-label>{{ category }}</ion-label>
+          <ion-item button *ngFor="let category of categories" (click)="selectCategory(category.id)">
+            <ion-label>{{ category.name }}</ion-label>
           </ion-item>
         </ion-list>
       </ion-col>
@@ -45,6 +44,29 @@
       </ion-col>
     </ion-grid>
   </ng-container>
+  <!-- <ng-container *ngIf="selectedSegment === 'collection'">
+    <ion-grid>
+      <ion-col size="2" class="fixed-sidebar">
+        <ion-list>
+          <ion-item button *ngFor="let category of categories" (click)="selectCategory(category.id)">
+            <ion-label>{{ category.name }}</ion-label>
+          </ion-item>
+        </ion-list>
+      </ion-col>
+      <ion-col size="10" class="scrollable-content">
+        <div class="book-cards-container">
+          <ion-card *ngFor="let book of filteredBooks" (click)="goToBookManagement(book.id)" class="book-card">
+            <ion-card-header>
+              <ion-card-title>{{ book.title }}</ion-card-title>
+            </ion-card-header>
+            <ion-card-content>
+              <p>{{ book.count }} 个条目</p>
+            </ion-card-content>
+          </ion-card>
+        </div>
+      </ion-col>
+    </ion-grid>
+  </ng-container> -->
 
   <ng-container *ngIf="selectedSegment === 'works' || selectedSegment === 'authors'">
     <div class="blank-page">
@@ -52,4 +74,4 @@
       <p *ngIf="selectedSegment === 'authors'">这里是作者页面的内容。</p>
     </div>
   </ng-container>
-</ion-content>
+</ion-content>

+ 5 - 3
poem-life-app/src/app/tab2/tab2.page.scss

@@ -88,12 +88,12 @@ ion-content {
    background-color: white;
    border-right: 1px solid #ddd;
    z-index: 1;
-   width: 250px; /* 增加宽度以防止遮挡 */
+   width: 30%; /* 增加宽度以防止遮挡 */
   }
 
   /* 可滚动的右侧内容 */
   .scrollable-content {
-   margin-left: 250px; /* 留出左侧分类列表的空间 */
+   margin-left: 20%; /* 留出左侧分类列表的空间 */
    height: calc(100vh - 56px); /* 减去工具栏高度 */
    overflow-y: auto;
    padding-top: 10px;
@@ -112,7 +112,7 @@ ion-content {
    grid-template-columns: repeat(3, 1fr); /* 每行显示三个卡片 */
    gap: 10px; // 设置卡片之间的间距
    padding: 10px;
-   margin-left: 100px; /* 扩大第一列卡片与左侧导航栏之间的间距 */
+   margin-left: 20%; /* 扩大第一列卡片与左侧导航栏之间的间距 */
   }
 
   /* 空白页面样式 */
@@ -124,3 +124,5 @@ ion-content {
    font-size: 1.2em;
    color: #888;
   }
+
+  

+ 206 - 63
poem-life-app/src/app/tab2/tab2.page.ts

@@ -1,82 +1,225 @@
-import { ApiService } from './../api.service';
 import { Component, OnInit } from '@angular/core';
-// import { IonHeader, IonToolbar, IonTitle, IonContent,IonButton, IonButtons  } from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-import { FormsModule } from '@angular/forms';
-import { CommonModule } from '@angular/common';
-import { IonicModule } from '@ionic/angular';
+import { HttpClient } from '@angular/common/http';
+import { Router } from '@angular/router';
 import { addIcons } from 'ionicons';
 import { search } from 'ionicons/icons';
-// import { Router } from 'express';
-import { Router } from '@angular/router';
+import { catchError, Observable, of, tap } from 'rxjs';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from '@ionic/angular';
+import { FormsModule } from '@angular/forms';
 
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
   styleUrls: ['tab2.page.scss'],
   standalone: true,
-  imports: [CommonModule, FormsModule,
-    IonicModule, ExploreContainerComponent]
+  imports: [CommonModule, IonicModule, FormsModule]
 })
 export class Tab2Page implements OnInit {
-  selectedSegment: string = 'collection';  // 默认选择分类标签
-  books: any[] = [];  // 所有书籍
-  filteredBooks: any[] = [];  // 筛选后的书籍
-  categories: string[] = [];  // 分类列表
-
-  categoryBooks: { [key: string]: any[] } = {};  // 动态存储各个类别的书籍
-  constructor(private apiService: ApiService,private router:Router) {
-    addIcons({search});
+  selectedSegment: string = 'collection'; // 默认选择分类标签
+  books: any[] = []; // 所有书籍
+  filteredBooks: any[] = []; // 筛选后的书籍
+  categories: any[] = []; // 分类列表
+
+  private baseUrl = 'http://localhost:3000/api'; // 后端 API 基础 URL
+  private loading = false; // 加载状态标志
+
+  constructor(private http: HttpClient, private router: Router) {
+    addIcons({ search });
   }
 
-  ngOnInit(){
-    // 假设此数据来自数据库
-    this.books = [
-      { id: 1, title: '唐诗全集' },
-      { id: 2, title: '诗经全集' },
-      // 可以从数据库获取书籍数据
-    ];
-    this.apiService.getBooks().subscribe(data=>{
-      this.books = data;
-      this.filteredBooks = this.books;
-    },error =>{
-      console.error('Error feching books',error);
+  ngOnInit() {
+    this.loadCategories();
+    this.loadBooks();
+  }
+
+  private getBooks(categoryId?: number): Observable<any[]> {
+    const url = `${this.baseUrl}/books${categoryId ? `?category_id=${categoryId}` : ''}`;
+    return this.http.get<any[]>(url).pipe(
+      catchError(error => {
+        console.error('Error fetching books', error);
+        return of([]);
+      })
+    );
+  }
+
+  private getCategories(): Observable<any[]> {
+    return this.http.get<any[]>(`${this.baseUrl}/categories`).pipe(
+      catchError(error => {
+        console.error('Error fetching categories', error);
+        return of([]);
+      })
+    );
+  }
+
+  loadCategories(): void {
+    this.getCategories().subscribe(data => {
+      this.categories = data;
     });
   }
 
-  segmentChanged(event: any) {
-      const newSegment = event.detail.value;
-      if (newSegment === 'collection') {
-       this.selectCategory(this.categories[0]); // 默认选择第一个类别
-      } else {
-       this.filteredBooks = []; // 清空书籍列表
-      }
-     }
-
-     selectBook(book: any) {
-      console.log('Selected book:', book);
-     }
-
-     openSearch() {
-      console.log('Search clicked');
-     }
-
-     selectCategory(category: string) {
-      console.log('Selected category:', category);
-      // 根据类别筛选书籍
-      if (this.categoryBooks[category]) {
-       this.filteredBooks = this.categoryBooks[category];
-      } else {
-       this.filteredBooks = this.books.filter(book => book.category === category);
-
-       // 如果没有匹配到任何书籍,显示提示信息
-       if (this.filteredBooks.length === 0) {
-        this.filteredBooks = [{ title: '暂无相关书籍', count: 0 }];
-       }
-      }
-     }
-     goToBookManagement(bookId: number) {
-      this.router.navigate(['/book-management', bookId]);
+  loadBooks(categoryId?: number): void {
+    this.loading = true;
+    this.getBooks(categoryId).pipe(
+      tap(data => {
+        this.books = data;
+        this.filteredBooks = categoryId ? data : this.books;
+      }),
+      tap(() => this.loading = false)
+    ).subscribe();
+  }
+
+  segmentChanged(event: any): void {
+    const newSegment = event.detail.value;
+    this.selectedSegment = newSegment;
+
+    if (newSegment === 'collection') {
+      this.filteredBooks = this.books;
+    } else {
+      this.filteredBooks = [];
+      // 根据需要添加其他逻辑
     }
+  }
+
+  selectCategory(categoryId: number): void {
+    console.log('Selected category ID:', categoryId);
+    this.loadBooks(categoryId); // 使用新的方法加载特定分类的书籍
+  }
+
+  goToBookManagement(bookId: number): void {
+    if (bookId) {
+      this.router.navigate(['book-management', bookId]);
+    } else {
+      console.warn('Invalid book ID');
     }
+  }
+
+  openSearch(): void {
+    console.log('Search clicked');
+  }
+}
+
+
+// import { Component, OnInit } from '@angular/core';
+// import { CommonModule } from '@angular/common';
+// import { IonicModule } from '@ionic/angular';
+// import { HttpClient } from '@angular/common/http';
+// import { Router } from '@angular/router';
+// import { addIcons } from 'ionicons';
+// import { search } from 'ionicons/icons';
+// import { FormsModule } from '@angular/forms';
+// import { catchError, of, tap ,Observable} from 'rxjs';
+
+// @Component({
+//   selector: 'app-tab2',
+//   templateUrl: 'tab2.page.html',
+//   styleUrls: ['tab2.page.scss'],
+//   standalone: true,
+//   imports: [CommonModule, IonicModule, FormsModule]
+// })
+// export class Tab2Page implements OnInit {
+//   selectedSegment: string = 'collection';  // 默认选择分类标签
+//   books: any[] = [];  // 所有书籍
+//   filteredBooks: any[] = [];  // 筛选后的书籍
+//   categories: any[] = [];  // 分类列表
+
+//   private baseUrl = 'http://localhost:3000/api'; // 后端 API 基础 URL
+//   private loading = false; // 加载状态标志
+
+//   constructor(private http: HttpClient, private router: Router) {
+//     addIcons({ search });
+//   }
+
+//   ngOnInit() {
+//     this.loadCategories();
+//     this.loadBooks();
+//   }
+
+//   loadCategories(): void {
+//     this.http.get<any[]>(`${this.baseUrl}/categories`)
+//       .pipe(
+//         tap(data => this.categories = data),
+//         catchError(error => {
+//           console.error('Error fetching categories', error);
+//           return of([]);
+//         })
+//       ).subscribe();
+//   }
+
+//   loadBooks(categoryId?: number): void {
+//     this.loading = true;
+//     this.http.get<any[]>(`${this.baseUrl}/books${categoryId ? `?category_id=${categoryId}` : ''}`)
+//       .pipe(
+//         tap(data => {
+//           this.books = data;
+//           this.filteredBooks = categoryId ? data : this.books;
+//         }),
+//         catchError(error => {
+//           console.error('Error fetching books', error);
+//           return of([]);
+//         }),
+//         tap(() => this.loading = false)
+//       ).subscribe();
+//   }
+
+//   // ngOnInit() {
+//   //   // 获取分类数据
+//   //   this.getCategories().subscribe((data: any[]) => {
+//   //     this.categories = data;  // 保存分类数据
+//   //   }, (error: any) => {
+//   //     console.error('Error fetching categories', error);
+//   //   });
+
+//   //   // 获取书籍数据
+//   //   this.getBooks().subscribe((data: any[]) => {
+//   //     this.books = data;
+//   //     this.filteredBooks = this.books;
+//   //   }, (error: any) => {
+//   //     console.error('Error fetching books', error);
+//   //   });
+//   // }
+
+//   getBooks(categoryId?: number) {
+//     const url = `${this.baseUrl}/books${categoryId ? `?category_id=${categoryId}` : ''}`;
+//     return this.http.get<any[]>(url); // 获取书籍数据
+//   }
+
+//   getCategories() {
+//     return this.http.get<any[]>(`${this.baseUrl}/categories`); // 获取分类数据
+//   }
+
+//   segmentChanged(event: any) {
+//     const newSegment = event.detail.value;
+//     if (newSegment === 'collection') {
+//       this.filteredBooks = this.books; // 默认显示所有书籍
+//     } else if (newSegment === 'works') {
+//       this.filteredBooks = []; // 清空书籍列表,您可以根据需要添加逻辑
+//     } else if (newSegment === 'authors') {
+//       this.filteredBooks = []; // 清空书籍列表,您可以根据需要添加逻辑
+//     }
+//   }
+  
+//   selectCategory(categoryId: number) {
+//     console.log('Selected category ID:', categoryId);
+//     // 根据类别获取书籍
+//     this.getBooks(categoryId).subscribe({
+//       next: data => {
+//         this.filteredBooks = data; // 更新书籍列表
+//       },
+//       error: err => {
+//         console.error('Error fetching books for category', err);
+//       },
+//       complete: () => {
+//         console.log('Request completed'); // 可选的完成回调
+//       }
+//     });
+//   }
+
+//   goToBookManagement(bookId: number) {
+//     this.router.navigate(['book-management', bookId]); // 直接传递 bookId
+//   }
 
+//   openSearch() {
+//     console.log('Search clicked');
+//   }
+// }

+ 140 - 48
poem-life-app/src/app/tab3/tab3.page.html

@@ -1,70 +1,162 @@
-<ion-content [fullscreen]="true">
-   <ion-header [translucent]="true">
+<ion-menu side="end" contentId="main-content">
+  <ion-header>
+    <ion-toolbar>
+      <ion-title>设置</ion-title>
+    </ion-toolbar>
+  </ion-header>
+  <ion-content class="ion-padding">
+
+<!-- 控件区域 -->
+<ion-list>
+  <ion-item>
+    <ion-label>安全隐私</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>地址信息</ion-label>
+  </ion-item>
+</ion-list>
+<div style="text-align: center; margin: 10px;"></div>
+<div style="text-align: center; margin: 10px;"></div>
+<ion-list>
+  <ion-item>
+    <ion-label>我的客服</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>关于我</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>商务合作</ion-label>
+  </ion-item>
+</ion-list>
+<div style="text-align: center; margin: 10px;"></div>
+<div style="text-align: center; margin: 10px;"></div>
+<!-- 版本号和关于区域 -->
+<ion-list>
+  <ion-item>
+    <ion-label>隐私政策</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>个人信息清单</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>第三方信息共享清单</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>用户协议</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>版本号</ion-label>
+    <ion-badge slot="end" color="primary">V6.0</ion-badge>
+  </ion-item>
+</ion-list>
+<!-- 退出登录按钮 -->
+<div style="text-align: center; margin: 30px;">
+  @if(!currentUser?.id){
+  <ion-button expand="block" (click)="signup()">注册</ion-button> 
+  <ion-button expand="block"  (click)="login()">登录</ion-button>
+  } 
+  @if(currentUser?.id){
+    <h1>您已登录成功</h1>
+  <ion-button expand="block" (click)="logout()">登出</ion-button>
+  }
+</div>
+  </ion-content>
+</ion-menu>
+
+<div class="ion-page" id="main-content">
+  <ion-header [translucent]="true">
     <ion-toolbar>
-     <ion-buttons slot="start">
-      <ion-menu-button></ion-menu-button>
-     </ion-buttons>
-     <ion-buttons slot="end">
-      <ion-button><ion-icon name="search"></ion-icon></ion-button>
-      <ion-button><ion-icon name="ellipsis-horizontal"></ion-icon></ion-button>
-     </ion-buttons>
+      <!-- <ion-buttons slot="start">
+        <ion-menu-button></ion-menu-button>
+      </ion-buttons> -->
+      <ion-buttons slot="end">
+        <ion-menu-button></ion-menu-button>
+        <!-- <ion-button (click)="navigateToLogin()">
+          <ion-icon name="search"></ion-icon>
+        </ion-button> -->
+        <!-- <ion-button (click)="navigateToLogin()">
+          <ion-icon name="ellipsis-horizontal"></ion-icon>
+        </ion-button> -->
+      </ion-buttons>
     </ion-toolbar>
-   </ion-header>
+  </ion-header>
+<ion-content [fullscreen]="true">
 
-   <div class="profile-container">
+
+  <div class="profile-container">
     <img src="../../assets/image/01/Vx_03.jpg" alt="背景图片" />
 
     <div class="profile-card">
-     <img src="#" alt="头像" class="avatar" />
-     <h2>{{ profile.name }}</h2>
-     <p *ngIf="profile.vipStatus">VIP 开通VIP&gt;</p >
-     <p>{{ profile.level }}</p >
+
+
+
+
+      @if(!currentUser?.id){
+      <!-- <button (click)="fileInput.click()" class="avatar-button">
+        <img src="currentUser.get("avatar")" alt="头像" class="avatar" />
+      </button>
+      <input type="file" #fileInput accept="image/*" (change)="onFileSelected($event)" style="display: none;" /> -->
+      <!-- <button (click)="editName()" class="name-button">
+        <h2>未知的某位网友</h2>
+      </button> -->
+      <div>未知的某位网友</div>
+    }
+    @if(currentUser?.id){
+      <!-- <button (click)="fileInput.click()" class="avatar-button">
+        <img [src]="profile.avatar" alt="头像" class="avatar" />
+      </button>
+      <input type="file" #fileInput accept="image/*" (change)="onFileSelected($event)" style="display: none;" />
+      <button (click)="editName()" class="name-button">
+        <h2>{{ currentUser?.get("realname")}}</h2>
+      </button> -->
+      <div>{{currentUser?.get("username")}}</div>
+    }
+
+
+
+      <p *ngIf="profile.vipStatus">VIP <button (click)="openVipPage()">开通VIP&gt;</button></p>
+      <!-- <p>{{ profile.level }}</p> -->
     </div>
 
-    <div class="cards-container">
-     <div class="card">
-      <ion-icon name="time-outline"></ion-icon>
-      <p>浏览记录</p >
-     </div>
-     <div class="card">
-      <ion-icon name="download-outline"></ion-icon>
-      <p>我的下载</p >
-     </div>
-     <div class="card">
-      <ion-icon name="cart-outline"></ion-icon>
-      <p>我的购买</p >
-     </div>
-     <div class="card">
-      <ion-icon name="wallet-outline"></ion-icon>
-      <p>我的钱包</p >
-     </div>
+    <div class="cards-container" (click)="!currentUser?.id ? showAlert() : editUser()">
+      <div class="card" *ngFor="let button of profile.buttons">
+        <ion-icon [name]="button.icon"></ion-icon>
+        <p>{{ button.text }}</p>
+      </div>
     </div>
-   </div>
 
-   <!-- 新增的喜欢和收藏按钮 -->
-   <div class="like-collection-buttons">
+
+  </div>
+
+  <div class="like-collection-buttons">
     <div class="card">
-     <button (click)="toggleTabs('like')" class="action-button">喜欢</button>
+      <button (click)="toggleTabs('like')" class="action-button">喜欢</button>
     </div>
     <div class="card">
-     <button (click)="toggleTabs('collect')" class="action-button">收藏</button>
+      <button (click)="toggleTabs('collect')" class="action-button">收藏</button>
     </div>
-   </div>
+  </div>
 
-   <div class="tabs-container" *ngIf="showTabs">
-      <ion-segment value="all">
-       <ion-segment-button (click)="setActiveTab('all')" [class.active]="activeTab === 'all'">
+  <div class="tabs-container" *ngIf="showTabs">
+    @if(!currentUser?.id){
+   <h1>未登录,没有查看权限</h1>
+  }
+  @if(currentUser?.id){
+    <ion-segment value="all">
+      <ion-segment-button (click)="setActiveTab('all')" [class.active]="activeTab === 'all'">
         <ion-icon name="images-outline"></ion-icon>
         <ion-label>全部</ion-label>
-       </ion-segment-button>
-       <ion-segment-button (click)="setActiveTab('image')" [class.active]="activeTab === 'image'">
+      </ion-segment-button>
+      <ion-segment-button (click)="setActiveTab('image')" [class.active]="activeTab === 'image'">
         <ion-icon name="image-outline"></ion-icon>
         <ion-label>图文</ion-label>
-       </ion-segment-button>
-       <ion-segment-button (click)="setActiveTab('document')" [class.active]="activeTab === 'document'">
+      </ion-segment-button>
+      <ion-segment-button (click)="setActiveTab('document')" [class.active]="activeTab === 'document'">
         <ion-icon name="document-text-outline"></ion-icon>
         <ion-label>文章</ion-label>
-       </ion-segment-button>
-      </ion-segment>
-     </div>
+      </ion-segment-button>
+    </ion-segment>
+  }
+  </div>
 </ion-content>
+</div>

+ 143 - 20
poem-life-app/src/app/tab3/tab3.page.ts

@@ -1,5 +1,5 @@
-import { Component } from '@angular/core';
-// import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { Component, ViewChild, ElementRef } from '@angular/core';
+import { Router } from '@angular/router';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { IonicModule } from '@ionic/angular';
 import { CommonModule } from '@angular/common';
@@ -7,45 +7,168 @@ import { addIcons } from 'ionicons';
 import { bookmarkOutline, cartOutline, documentTextOutline, downloadOutline, ellipsisHorizontal, heartOutline, imageOutline, imagesOutline, search, timeOutline, walletOutline } from 'ionicons/icons';
 
 
+
+import { EnvironmentInjector,inject } from '@angular/core';
+import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.component';
+import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { CloudUser } from 'src/lib/ncloud';
+import { ModalController } from '@ionic/angular/standalone';
+import { AlertController } from '@ionic/angular/standalone'; // 导入所需的模块
+
 @Component({
   selector: 'app-tab3',
   templateUrl: 'tab3.page.html',
   styleUrls: ['tab3.page.scss'],
   standalone: true,
-  imports: [ExploreContainerComponent,
-    IonicModule,
-    CommonModule
-  ],
+  imports: [ExploreContainerComponent, IonicModule, CommonModule],
 })
 export class Tab3Page {
   profile = {
     name: 'X',
     level: 'X',
     vipStatus: true,
+    avatar: '../../assets/image/default-avatar.png', // 默认头像路径
     buttons: [
-        { icon: 'time-outline', text: '浏览记录' },
-        { icon: 'download-outline', text: '我的下载' },
-        { icon: 'cart-outline', text: '我的购买' },
-        { icon: 'wallet-outline', text: '我的钱包' }
+      { icon: 'time-outline', text: '浏览记录' },
+      { icon: 'download-outline', text: '我的下载' },
+      { icon: 'cart-outline', text: '我的购买' },
+      { icon: 'wallet-outline', text: '我的钱包' }
     ],
     tabs: [
-        { icon: 'images-outline', text: '全部' },
-        { icon: 'image-outline', text: '图文' },
-        { icon: 'document-text-outline', text: '文章' }
+      { icon: 'images-outline', text: '全部' },
+      { icon: 'image-outline', text: '图文' },
+      { icon: 'document-text-outline', text: '文章' }
     ]
   };
 
-  showTabs = false;;
-  activeTab : string = 'all';
-  constructor() {
+  showTabs = false;
+  activeTab: string = 'all';
+  public environmentInjector = inject(EnvironmentInjector);
+
+  currentUser:CloudUser|undefined
+  constructor(private router: Router,
+
+    private alertController: AlertController,
+    private modalCtrl:ModalController) {
+    this.currentUser = new CloudUser();
     addIcons({search, ellipsisHorizontal, timeOutline, downloadOutline, cartOutline, walletOutline, heartOutline, bookmarkOutline, imageOutline, imagesOutline, documentTextOutline});
   }
 
   toggleTabs(action: string) {
-    this.showTabs =true;
-    this.activeTab = action === 'like' ? 'all' :'collect';
+    this.showTabs = true;
+    this.activeTab = action === 'like' ? 'all' : 'collect';
+  }
+
+  setActiveTab(tab: string) {
+    this.activeTab = tab;
+  }
+
+  navigateToLogin() {
+    this.router.navigate(['/tabs/login']);
+  }
+
+  editName() {
+    const newName = prompt("请输入新的名字", this.profile.name); // 仅示例
+    if (newName) {
+      this.profile.name = newName;
+    }
+  }
+
+  openVipPage() {
+    // 这里可以添加逻辑来导航到VIP开通页面
+    alert("导航到VIP开通页面");
+  }
+
+  onFileSelected(event: Event) {
+    const input = event.target as HTMLInputElement;
+    if (input.files && input.files[0]) {
+      const file = input.files[0];
+      const reader = new FileReader();
+
+      reader.onload = (e: any) => {
+        this.profile.avatar = e.target.result; // 将读取的图片设置为头像
+      };
+
+      reader.readAsDataURL(file); // 读取文件为Data URL
+    }
+  }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 
+
+
+
+  async login(){
+    // 弹出登录窗口
+    let user = await openUserLoginModal(this.modalCtrl);
+    if(user?.id){
+      this.currentUser = user
+    }
+  }
+  async signup(){
+    // 弹出注册窗口
+    let user = await openUserLoginModal(this.modalCtrl,"signup");
+    if(user?.id){
+      this.currentUser = user
+    }
+  }
+  logout(){
+    this.currentUser?.logout();
   }
-  setActiveTab(tab:string) {
-   this.activeTab = tab;
+  editUser(){
+    openUserEditModal(this.modalCtrl)
   }
+editTags:Array<String>=[]
+    async setTagsValue(ev:any){
+     let currentUser = new CloudUser();
+     let userPrompt = ``
+     if(!currentUser?.id){
+       console.log("用户未登录,请登录后重试");
+       let user = await openUserLoginModal(this.modalCtrl);
+       if(!user?.id){
+         return
+       }
+       currentUser = user;
+     }
+   //console.log("setTagsValue",ev);
+   this.editTags=ev;
+ }
+ async showAlert() {
+  const alert = await this.alertController.create({
+    header: '提示', // 提示框的标题
+    message: '请先登录以查看个人信息。', // 提示框的消息
+    buttons: ['确定'], // 按钮文本,用户点击后关闭提示框
+  });
+
+  await alert.present(); // 显示提示框
 }
+
+
+
+
+
+}

+ 23 - 9
poem-life-app/src/app/tab4/tab4.page.html

@@ -5,12 +5,26 @@
 </ion-header>
 
 <ion-content [fullscreen]="true">
-  <ion-list>
-    <ion-button (click)="goToCreate()"><span>AI创作</span></ion-button>
-    <ion-button (click)="goToCreatePic()"><span>AI绘图</span></ion-button>
-  </ion-list>
-
-  <p *ngIf="message">
-    {{ message.message }}
-  </p>
-</ion-content>
+  <div class="content-wrapper">
+    <div class="main-content">
+      <div class="card-section">
+        <div class="cards">
+          <div class="card" (click)="goToCreate()">
+            <div class="card-image" style="background-image: url('../../assets/image/05/shipian.jpg');"></div>
+            <div class="card-content">
+              <h4>AI创作</h4>
+              <p>点击进入AI创作页面</p>
+            </div>
+          </div>
+          <div class="card" (click)="goToCreatePic()">
+            <div class="card-image" style="background-image: url('../../assets/image/05/huihua.jpg');"></div>
+            <div class="card-content">
+              <h4>AI绘图</h4>
+              <p>点击进入AI绘图页面</p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</ion-content>

+ 52 - 0
poem-life-app/src/app/tab4/tab4.page.scss

@@ -0,0 +1,52 @@
+.content-wrapper {
+    padding: 16px;
+  }
+  
+  .main-content {
+    .card-section {
+      margin-bottom: 20px;
+  
+      .cards {
+        display: flex;
+        flex-direction: column; /* 使卡片纵向排列 */
+        gap: 20px; /* 卡片之间的间距 */
+      }
+  
+      .card {
+        background: rgba(255, 255, 255, 0.8); /* 半透明背景以增强文字可读性 */
+        border-radius: 8px;
+        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+        cursor: pointer;
+        transition: transform 0.2s;
+        width: calc(100% - 32px); /* 使卡片宽度略小于页面 */
+        margin: 0 auto; /* 使卡片居中 */
+        min-height: 500px; /* 设置最小高度,确保有足够空间放置内容 */
+  
+        .card-image {
+          height: 400px; /* 设置图片区域的高度 */
+          background-size: contain; /* 背景图像覆盖,保持等比例缩放 */
+          background-position: center; /* 背景图像居中 */
+          border-top-left-radius: 8px; /* 圆角 */
+          border-top-right-radius: 8px; /* 圆角 */
+        }
+  
+        .card-content {
+          padding: 10px; /* 内容的内边距 */
+          text-align: center; /* 内容居中 */
+        }
+  
+        h4 {
+          margin: 0 0 10px; /* 标题下方的间距 */
+        }
+  
+        p {
+          margin: 0; /* 描述的外边距 */
+          color: #666; /* 描述的颜色 */
+        }
+  
+        &:hover {
+          transform: scale(1.05);
+        }
+      }
+    }
+  }

+ 17 - 15
poem-life-app/src/app/tab4/tab4.page.ts

@@ -10,26 +10,28 @@ import { Router } from '@angular/router';
   templateUrl: './tab4.page.html',
   styleUrls: ['./tab4.page.scss'],
   standalone: true,
-  imports: [IonContent, IonHeader,IonList,IonButton, IonTitle, IonToolbar, CommonModule, FormsModule]
+  imports: [IonContent, IonHeader, IonList, IonButton, IonTitle, IonToolbar, CommonModule, FormsModule]
 })
 export class Tab4Page implements OnInit {
   title = 'frontEnd';
   message: any;
-  constructor(private router:Router,private apiService:ApiService) {
-  }
-  goToCreate(){
-    this.router.navigateByUrl('/tabs/create')
+  
+  constructor(private router: Router, private apiService: ApiService) {}
+
+  goToCreate() {
+    this.router.navigateByUrl('/tabs/create');
   }
-  goToCreatePic(){
-    this.router.navigateByUrl('/tabs/createpic')
+
+  goToCreatePic() {
+    this.router.navigateByUrl('/tabs/createpic');
   }
+
   ngOnInit() {
-    this.apiService.getMessage().subscribe(data => {
-      console.log(data);  // 输出返回的数据
-      this.message = data;
-    }, error => {
-      console.error('Error fetching message:', error); // 输出错误消息
-    });
+    // this.apiService.getMessage().subscribe(data => {
+    //   console.log(data);  // 输出返回的数据
+    //   this.message = data;
+    // }, error => {
+    //   console.error('Error fetching message:', error); // 输出错误消息
+    // });
   }
-
-}
+}

+ 195 - 0
poem-life-app/src/app/tab5/tab5.component.html

@@ -0,0 +1,195 @@
+<ion-menu side="end" contentId="main-content">
+  <ion-header>
+    <ion-toolbar>
+      <ion-title>设置</ion-title>
+    </ion-toolbar>
+  </ion-header>
+  <ion-content class="ion-padding">
+
+<!-- 控件区域 -->
+<ion-list>
+  <ion-item>
+    <ion-label>安全隐私</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>地址信息</ion-label>
+  </ion-item>
+</ion-list>
+<div style="text-align: center; margin: 10px;"></div>
+<div style="text-align: center; margin: 10px;"></div>
+<ion-list>
+  <ion-item>
+    <ion-label>我的客服</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>关于我</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>商务合作</ion-label>
+  </ion-item>
+</ion-list>
+<div style="text-align: center; margin: 10px;"></div>
+<div style="text-align: center; margin: 10px;"></div>
+<!-- 版本号和关于区域 -->
+<ion-list>
+  <ion-item>
+    <ion-label>隐私政策</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>个人信息清单</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>第三方信息共享清单</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>用户协议</ion-label>
+  </ion-item>
+  <ion-item>
+    <ion-label>版本号</ion-label>
+    <ion-badge slot="end" color="primary">V6.0</ion-badge>
+  </ion-item>
+</ion-list>
+<!-- 退出登录按钮 -->
+<div style="text-align: center; margin: 30px;">
+
+  <ion-button expand="block" (click)="logout()">登出</ion-button>
+</div>
+  </ion-content>
+</ion-menu>
+
+<div class="ion-page" id="main-content">
+  <ion-header>
+    <ion-toolbar>
+      <ion-title>我的</ion-title>
+      <ion-buttons slot="end">
+        <ion-menu-button></ion-menu-button>
+      </ion-buttons>
+    </ion-toolbar>
+  </ion-header>
+<ion-content>   
+    <div class="box">
+      <div class="top">
+        <div class="region">
+          <div class="upper">
+            @if(!currentUser?.id){
+            <div class="img-box">
+              <img src="../../assets/icon/head1.png" alt="User Image" />
+            </div>
+            <div class="info">
+              <div class="name">未登录</div>
+              <div class="no">***********</div>
+            </div>
+          }
+          @if(currentUser?.id){
+            <div class="img-box">
+              <img src="../../assets/icon/head1.png" alt="User Image" />
+            </div>
+            <div class="info">
+              <div class="name">{{currentUser?.get("realname")}}</div>
+              <div class="no">邮箱:{{currentUser?.get("email")||"-"}}</div>
+            </div>
+            }
+          </div>
+        </div>
+        @if(!currentUser?.id){
+          <div class="lower1">
+  
+          <ion-button expand="block" class="custom-button" (click)="login()">登录</ion-button>
+          <ion-button expand="block"class="custom-button"  (click)="signup()">注册</ion-button>  
+        </div>
+        }
+        @if(currentUser?.id){
+        <div class="lower">落霞与孤鹜齐飞,天水共长天一色</div>
+        <div class="count">
+          <div class="title">
+            <span>关注:0</span>
+            <span>粉丝:0</span>
+            <span>获赞:99</span>
+          </div>
+        </div>
+      }
+      </div>
+ </div>
+<div class="icon-container">
+  <div class="icon-item">
+    <ion-icon name="folder-outline"></ion-icon>
+    <span>离线缓存</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="time-outline"></ion-icon>
+    <span>历史记录</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="star-outline"></ion-icon>
+    <span>我的收藏</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="heart-outline"></ion-icon>
+    <span>我的喜欢</span>
+  </div>
+</div>
+
+<div style="margin-top: 60px;width: 100%;padding-left: 20px;"><p class="left-align">更多服务:</p></div>
+<div class="icon-container">
+  <div class="icon-item">
+    <ion-icon name="folder-outline"></ion-icon>
+    <span>离线缓存</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="time-outline"></ion-icon>
+    <span>历史记录</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="star-outline"></ion-icon>
+    <span>我的收藏</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="heart-outline"></ion-icon>
+    <span>我的喜欢</span>
+  </div>
+</div>
+<div class="icon-container">
+  <div class="icon-item">
+    <ion-icon name="folder-outline"></ion-icon>
+    <span>离线缓存</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="time-outline"></ion-icon>
+    <span>历史记录</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="star-outline"></ion-icon>
+    <span>我的收藏</span>
+  </div>
+  <div class="icon-item">
+    <ion-icon name="heart-outline"></ion-icon>
+    <span>我的喜欢</span>
+  </div>
+</div>
+<div style="margin-top: 60px;width: 100%;">
+  <div style="width: 100%;padding-left: 20px;"><p class="left-align">更多服务:</p></div>
+<ion-list lines="none">
+  <ion-item (click)="!currentUser?.id ? showAlert() : editUser()">
+    <ion-icon slot="start" name="person-outline" style="font-size: 20px;"></ion-icon>
+    <ion-label>个人信息</ion-label>
+    <ion-icon slot="end" name="chevron-forward-outline" style="color: lightgray;"></ion-icon>
+  </ion-item>
+  <ion-item>
+    <ion-icon slot="start" name="shield-outline"style="font-size: 20px;"></ion-icon>
+    <ion-label>安全隐私</ion-label>
+    <ion-icon slot="end" name="chevron-forward-outline" style="color: lightgray;"></ion-icon>
+  </ion-item>
+  <ion-item>
+    <ion-icon slot="start" name="call-outline"style="font-size: 20px;"></ion-icon>
+    <ion-label>联系客服</ion-label>
+    <ion-icon slot="end" name="chevron-forward-outline" style="color: lightgray;"></ion-icon>
+  </ion-item>
+  <ion-item>
+    <ion-icon slot="start" name="lock-closed-outline"style="font-size: 20px;"></ion-icon>
+    <ion-label>隐私政策</ion-label>
+    <ion-icon slot="end" name="chevron-forward-outline" style="color: lightgray;"></ion-icon>
+  </ion-item>
+</ion-list>
+</div>
+</ion-content>
+</div>

+ 178 - 0
poem-life-app/src/app/tab5/tab5.component.scss

@@ -0,0 +1,178 @@
+.bg-color {
+    --background: #f4f4f4;  /* 设置背景颜色 */
+  }
+  
+  .top-list {
+    margin-top: 10px;
+  }
+  
+  ion-item {
+    --inner-padding-end: 10px;
+  }
+  
+  ion-avatar {
+    width: 40px;
+    height: 40px;
+  }
+  
+  ion-label h2 {
+    font-size: 16px;
+    font-weight: bold;
+  }
+  
+  ion-label p {
+    font-size: 14px;
+    color: #888;
+  }
+  
+  ion-button {
+    margin-top: 20px;
+  }
+  
+
+
+
+  .box {
+    width: 100%;
+    margin: auto;
+  }
+  
+  .top {
+    position: relative;
+    padding-top: 30px; /* 使用 px 替代 rpx */
+    width: 100%;
+    height: 300px; /* 使用 px 替代 rpx */
+  }
+  
+  .top:after {
+    width: 140%;
+    height:300px;
+    position: absolute;
+    left: -20%;
+    top: 0;
+    z-index: -1;
+    content: '';
+    border-radius: 0 0 60% 60%;
+    background-color: #426db5;
+  }
+  
+  .region {
+    margin:auto;
+    width: 450px;
+  }
+  
+  .upper {
+  
+      display: flex; /* 使用弹性盒布局,使子元素能够灵活排列 */
+      justify-content: center; /* 在主轴上居中对齐子元素 */
+     align-items: center; /* 在交叉轴上居中对齐子元素 */
+  
+  }
+  
+  .img-box {
+    width: 120px;
+    height: 120px; 
+    border-radius: 50%; /* 设置边框半径为 50%,使盒子变为圆形 */
+    overflow: hidden; /* 确保任何超出部分都被隐藏 */
+  }
+  
+  .img-box img {
+    width: 100%;
+    height: 100%;
+  }
+  
+  .info {
+    padding-left: 30px; /* 设置左侧内边距为 30 像素 */
+    line-height: 55px; /* 设置行高为 55 像素,增加文本行之间的垂直间距 */
+    flex-direction: column; /* 将子元素排列为纵向(列)方向 */
+}
+  
+.name {
+  font-size: 20px; /* 设置字体大小为 40 像素 */
+  letter-spacing: 5px; /* 设置字母间距为 5 像素 */
+  color: #FFFFFF; /* 设置字体颜色为白色 */
+}
+  
+.no {
+  font-size: 18px; /* 设置字体大小为 24 像素 */
+  letter-spacing: 2px; /* 设置字母间距为 2 像素 */
+  color: #b3dffe; /* 设置字体颜色为淡蓝色 */
+}
+.lower {
+  display: flex;
+  justify-content: center;
+  line-height: 100px; 
+  font-size: 20px; 
+  color: #6b37f0;
+}
+.lower1 {
+  width: 200px;
+  height: 10px;
+  display: flex;
+  margin: auto;
+  flex-direction: column; /* 设置为垂直方向 */
+}
+
+.custom-button {
+  color: #660dd4;
+}
+
+.custom-button:hover {
+  --background: rgba(255, 255, 255, 0.7); /* 悬停时背景颜色变亮 */
+}
+
+.count {
+  display: flex; /* 使用弹性盒布局,使子元素能够灵活排列 */
+  margin: auto;
+  width: 200px; /* 设置元素的宽度为 350 像素 */
+  line-height: 16px; /* 设置行高为 80 像素,增加文本行之间的垂直间距 */
+  border-radius: 50px; /* 设置边框半径为 50 像素,使元素的角变圆润 */
+  background-color:   #426db5;
+  // box-shadow: 0 3px 9px #bdbdbd; /* 添加阴影效果,使元素看起来有浮起的效果 */
+}
+
+.title {
+  width: auto;
+  display: flex; /* 将元素设置为弹性盒布局,使子元素能够灵活排列 */
+  justify-content: center; /* 在主轴上居中对齐子元素 */
+  align-items: center; /* 在交叉轴上居中对齐子元素 */
+}
+
+.title img {
+  width: 30px; 
+  height: auto;
+}
+
+.title span {
+  font-size: 16px; 
+  padding-left: 15px; 
+  color: #b3dffe;
+}
+
+
+.icon-container {
+  margin-top: 10px;
+  display: flex; /* 使用弹性盒布局 */
+  justify-content: space-around; /* 在主轴上均匀分配空间 */
+}
+
+.icon-container1 {
+  margin-top: 40px;
+  display: flex; /* 使用弹性盒布局 */
+  justify-content: space-around; /* 在主轴上均匀分配空间 */
+}
+
+.icon-item {
+  text-align: center; /* 使文本居中对齐 */
+}
+
+.icon-item ion-icon {
+  font-size: 25px; /* 设置图标大小 */
+  margin-bottom: 10px; /* 图标与文本之间的间距 */
+}
+
+.icon-item span {
+  display: block; /* 将文本设置为块级元素,以便于控制布局 */
+  font-size: 16px; /* 设置文本大小 */
+  color: #333; /* 设置文本颜色 */
+}

+ 22 - 0
poem-life-app/src/app/tab5/tab5.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { Tab5Component } from './tab5.component';
+
+describe('Tab5Component', () => {
+  let component: Tab5Component;
+  let fixture: ComponentFixture<Tab5Component>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [Tab5Component],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(Tab5Component);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 91 - 0
poem-life-app/src/app/tab5/tab5.component.ts

@@ -0,0 +1,91 @@
+import { Component,EnvironmentInjector,inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonBadge, IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonItem, IonList, IonMenu, IonMenuButton, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+import { IonLabel } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import {folderOutline,timeOutline,starOutline,heartOutline,chevronForwardOutline,personOutline,shieldOutline,callOutline,lockClosedOutline} from 'ionicons/icons';
+addIcons({folderOutline,timeOutline,starOutline,heartOutline,chevronForwardOutline,personOutline,shieldOutline,callOutline,lockClosedOutline})
+
+import { CloudUser } from 'src/lib/ncloud';
+import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.component';
+import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { Router } from '@angular/router';
+import { ModalController } from '@ionic/angular/standalone';
+import { AlertController } from '@ionic/angular/standalone'; // 导入所需的模块
+
+@Component({
+  selector: 'app-tab5',
+  templateUrl: './tab5.component.html',
+  styleUrls: ['./tab5.component.scss'],
+  standalone: true,
+  imports: [CommonModule,IonIcon,IonLabel,IonList,IonButton,IonMenu,IonToolbar,IonMenuButton,IonTitle,IonButtons,IonItem,IonHeader,IonContent,IonBadge
+  ],
+})
+export class Tab5Component{
+
+  public environmentInjector = inject(EnvironmentInjector);
+
+ currentUser:CloudUser|undefined
+   constructor(
+    private alertController: AlertController,
+     private router: Router,
+     private modalCtrl:ModalController) {
+     addIcons({ folderOutline,timeOutline,starOutline,heartOutline});
+     this.currentUser = new CloudUser();
+   }
+   async login(){
+     // 弹出登录窗口
+     let user = await openUserLoginModal(this.modalCtrl);
+     if(user?.id){
+       this.currentUser = user
+     }
+   }
+   async signup(){
+     // 弹出注册窗口
+     let user = await openUserLoginModal(this.modalCtrl,"signup");
+     if(user?.id){
+       this.currentUser = user
+     }
+   }
+   logout(){
+     this.currentUser?.logout();
+   }
+ 
+   editUser(){
+     openUserEditModal(this.modalCtrl)
+   }
+ 
+   editTags:Array<String>=[]
+    async setTagsValue(ev:any){
+     let currentUser = new CloudUser();
+     let userPrompt = ``
+     if(!currentUser?.id){
+       console.log("用户未登录,请登录后重试");
+       let user = await openUserLoginModal(this.modalCtrl);
+       if(!user?.id){
+         return
+       }
+       currentUser = user;
+     }
+   //console.log("setTagsValue",ev);
+   this.editTags=ev;
+ }
+
+
+
+
+
+
+
+
+ 
+ async showAlert() {
+  const alert = await this.alertController.create({
+    header: '提示', // 提示框的标题
+    message: '请先登录以查看个人信息。', // 提示框的消息
+    buttons: ['确定'], // 按钮文本,用户点击后关闭提示框
+  });
+
+  await alert.present(); // 显示提示框
+}
+}

+ 10 - 0
poem-life-app/src/app/tabs/tabs.routes.ts

@@ -25,6 +25,11 @@ export const routes: Routes = [
         path: 'tab4',
         loadComponent: () => import('../tab4/tab4.page').then( m => m.Tab4Page)
       },
+      {
+        path: 'tab5',
+        loadComponent: () =>
+          import('../tab5/tab5.component').then((m) => m.Tab5Component),
+      },
       {
         path: 'create',
         loadComponent: () => import('../page-create/page-create.component').then( m => m.PageCreateComponent)
@@ -33,6 +38,11 @@ export const routes: Routes = [
         path: 'createpic',
         loadComponent: () => import('../page-createpic/page-createpic.component').then( m => m.PageCreatepicComponent)
       },
+      
+      {
+        path: 'login',  // 添加 login 页面路径
+        loadComponent: () => import('../login/login.component').then(m => m.LoginComponent)
+      },
       {
         path: '',
         redirectTo: '/tabs/tab1',

二進制
poem-life-app/src/assets/image/05/huihua.jpg


二進制
poem-life-app/src/assets/image/05/shipian.jpg


二進制
poem-life-app/src/assets/image/06/beijing.png


二進制
poem-life-app/src/assets/image/06/login.jpg


+ 3 - 0
poem-life-app/src/global.scss

@@ -35,3 +35,6 @@
 /* @import "@ionic/angular/css/palettes/dark.always.css"; */
 /* @import "@ionic/angular/css/palettes/dark.class.css"; */
 @import '@ionic/angular/css/palettes/dark.system.css';
+
+
+

+ 320 - 0
poem-life-app/src/lib/ncloud.ts

@@ -0,0 +1,320 @@
+// CloudObject.ts
+export class CloudObject {
+    className: string;
+    id: string | null = null;
+    createdAt:any;
+    updatedAt:any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    whereOptions: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    greaterThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        this.whereOptions[key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        return json || {};
+    }
+
+    async find() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.whereOptions).length) {
+            const whereStr = JSON.stringify(this.whereOptions);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item:any)=>this.dataToObj(item))
+        return objList || [];
+    }
+
+    async first() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.whereOptions).length) {
+            const whereStr = JSON.stringify(this.whereOptions);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists:any):CloudObject{
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if(userCacheStr){
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken:string|null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/users/me`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+            },
+            method: "GET"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+        return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string):Promise<CloudUser|null> {
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+        
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return false;
+        }
+
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = null;
+        this.sessionToken = null;
+        this.data = {};
+        return true;
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
+}

+ 29 - 0
poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.html

@@ -0,0 +1,29 @@
+<!-- 用户登录状态 -->
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>
+      用户名:{{currentUser?.get("username")}}
+    </ion-card-title>
+    <ion-card-subtitle>请输入您的详细资料</ion-card-subtitle>
+   </ion-card-header>
+ <ion-card-content>
+
+   <ion-item>
+     <ion-input [value]="userData['realname']" (ionChange)="userDataChange('realname',$event)" label="姓名" placeholder="请您输入真实姓名"></ion-input>
+   </ion-item>
+   <ion-item>
+     <ion-input type="number" [value]="userData['age']" (ionChange)="userDataChange('age',$event)" label="年龄" placeholder="请您输入年龄"></ion-input>
+    </ion-item>
+  <ion-item>
+     <ion-input [value]="userData['gender']" (ionChange)="userDataChange('gender',$event)" label="性别" placeholder="请您输入男/女"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-input [value]="userData['avatar']" (ionChange)="userDataChange('avatar',$event)" label="头像" placeholder="请您输入头像地址"></ion-input>
+     </ion-item>
+
+   <ion-button expand="block" (click)="save()">保存</ion-button>
+   <ion-button expand="block" (click)="cancel()">取消</ion-button>
+ 
+
+</ion-card-content>
+</ion-card>

+ 0 - 0
poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss


+ 22 - 0
poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ModalUserEditComponent } from './modal-user-edit.component';
+
+describe('ModalUserEditComponent', () => {
+  let component: ModalUserEditComponent;
+  let fixture: ComponentFixture<ModalUserEditComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [ModalUserEditComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ModalUserEditComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 73 - 0
poem-life-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts

@@ -0,0 +1,73 @@
+import { Input, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-modal-user-edit',
+  templateUrl: './modal-user-edit.component.html',
+  styleUrls: ['./modal-user-edit.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonInput,IonItem,
+    IonSegment,IonSegmentButton,IonLabel,
+  ],
+})
+export class ModalUserEditComponent  implements OnInit {
+
+  currentUser:CloudUser|undefined
+  userData:any = {}
+  userDataChange(key:string,ev:any){
+    let value = ev?.detail?.value
+    if(value){
+      this.userData[key] = value
+    }
+  }
+  constructor(private modalCtrl:ModalController) { 
+    this.currentUser = new CloudUser();
+    this.userData = this.currentUser.data;
+  }
+
+  ngOnInit() {}
+
+  async save(){
+    Object.keys(this.userData).forEach(key=>{
+      if(key=="age"){
+        this.userData[key] = Number(this.userData[key])
+      }
+    })
+
+    this.currentUser?.set(this.userData)
+    await this.currentUser?.save()
+    this.modalCtrl.dismiss(this.currentUser,"confirm")
+  }
+  cancel(){
+    this.modalCtrl.dismiss(null,"cancel")
+
+  }
+}
+// 导出一个异步函数,用于打开用户编辑模态框并返回编辑后的用户信息
+export async function openUserEditModal(modalCtrl: ModalController): Promise<CloudUser | null> {
+  
+  // 创建模态框实例,指定要显示的组件和模态框的高度设置
+  const modal = await modalCtrl.create({
+    component: ModalUserEditComponent, // 指定模态框中要显示的组件
+    breakpoints: [0.7, 1.0], // 设置模态框的可调整高度的断点
+    initialBreakpoint: 1.0 // 设置模态框初始显示的高度为 70%
+  });
+
+  // 显示模态框
+  modal.present();
+
+  // 等待模态框关闭,并获取关闭时传递的数据和角色
+  const { data, role } = await modal.onWillDismiss();
+
+  // 如果模态框是通过确认操作关闭,返回传递的数据
+  if (role === 'confirm') {
+    return data; // 返回用户编辑后的数据
+  }
+
+  // 如果模态框是通过取消或其他方式关闭,返回 null
+  return null;
+}

+ 36 - 0
poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.html

@@ -0,0 +1,36 @@
+<!-- 用户登录状态 -->
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>
+      <ion-segment [value]="type" (ionChange)="typeChange($event)">
+        <ion-segment-button value="login">
+          <ion-label>登录</ion-label>
+        </ion-segment-button>
+        <ion-segment-button value="signup">
+          <ion-label>注册</ion-label>
+        </ion-segment-button>
+      </ion-segment>
+    </ion-card-title>
+    <ion-card-subtitle>请输入账号密码</ion-card-subtitle>
+  </ion-card-header>
+  <ion-card-content>
+    <ion-item>
+      <ion-input [value]="username" (ionChange)="usernameChange($event)" label="账号" placeholder="请您输入账号/手机号"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-input [value]="password" (ionChange)="passwordChange($event)" label="密码" type="password" value="password"></ion-input>
+    </ion-item>
+
+    @if(type=="signup"){
+      <ion-item>
+        <ion-input [value]="password2" (ionChange)="password2Change($event)" label="密码二次" type="password" value="password"></ion-input>
+      </ion-item>
+    }
+    @if(type=="login"){
+      <ion-button expand="block" (click)="login()">登录</ion-button>
+    }
+    @if(type=="signup"){
+      <ion-button expand="block" (click)="signup()">注册</ion-button>
+    }
+  </ion-card-content>
+</ion-card>

+ 0 - 0
poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.scss


+ 22 - 0
poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ModalUserLoginComponent } from './modal-user-login.component';
+
+describe('ModalUserLoginComponent', () => {
+  let component: ModalUserLoginComponent;
+  let fixture: ComponentFixture<ModalUserLoginComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [ModalUserLoginComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ModalUserLoginComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 94 - 0
poem-life-app/src/lib/user/modal-user-login/modal-user-login.component.ts

@@ -0,0 +1,94 @@
+import { Input, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-modal-user-login',
+  templateUrl: './modal-user-login.component.html',
+  styleUrls: ['./modal-user-login.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonInput,IonItem,
+    IonSegment,IonSegmentButton,IonLabel
+  ],
+})
+export class ModalUserLoginComponent  implements OnInit {
+  @Input()
+  type:"login"|"signup" = "login"
+  typeChange(ev:any){
+    this.type = ev?.detail?.value || ev?.value || 'login'
+  }
+  username:string = ""
+  usernameChange(ev:any){
+    console.log(ev)
+    this.username = ev?.detail?.value
+  }
+  password:string = ""
+  passwordChange(ev:any){
+    this.password = ev?.detail?.value
+  }
+  password2:string = ""
+  password2Change(ev:any){
+    this.password2 = ev?.detail?.value
+  }
+  constructor(private modalCtrl:ModalController) {
+    console.log(this.type)
+   }
+
+  ngOnInit() {}
+
+  async login(){
+    if(!this.username || !this.password){
+      console.log("请输入完整")
+      return
+    }
+    let user:any = new CloudUser();
+    user = await user.login(this.username,this.password);
+    if(user?.id){
+       this.modalCtrl.dismiss(user,"confirm")
+    }else{
+      console.log("登录失败")
+    }
+  }
+
+  async signup(){
+    if(!this.username || !this.password || !this.password2){
+      console.log("请输入完整")
+      return
+    }
+    if(this.password!=this.password2){
+      console.log("两次密码不符,请修改")
+      return
+    }
+
+    let user:any = new CloudUser();
+    user = await user.signUp(this.username,this.password);
+    if(user){
+      this.type = "login"
+      console.log("注册成功请登录")
+    }
+  }
+
+}
+
+
+export async function openUserLoginModal(modalCtrl:ModalController,type:"login"|"signup"="login"):Promise<CloudUser|null>{
+  const modal = await modalCtrl.create({
+    component: ModalUserLoginComponent,
+    componentProps:{
+      type:type
+    },
+    breakpoints:[0.5,0.7],
+    initialBreakpoint:0.5
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}

+ 42 - 36
poem-life-serve/db/dbServe.js

@@ -1,12 +1,4 @@
-/*
- * @Author: 危齐晟 1913361097@qq.com
- * @Date: 2024-12-17 10:31:46
- * @LastEditors: 危齐晟 1913361097@qq.com
- * @LastEditTime: 2024-12-17 15:24:58
- * @FilePath: \202226701045\poem-life-serve\db\dbServe.js
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
-// server.js
+
 const express = require('express');
 const mysql = require('mysql2');
 const cors = require('cors');
@@ -24,7 +16,7 @@ const db = mysql.createConnection({
     user: 'root', // 替换为您的MySQL用户名
     password: '123456', // 替换为您的MySQL密码
     database: 'poemLifeApp',
-    ssl: false  // 禁用SSL连接(如果不需要的话)
+    ssl: false  // 禁用SSL连接(如果不需要
 });
 
 // 连接到数据库
@@ -38,7 +30,25 @@ db.connect(err => {
 
 // 获取书籍列表的 API
 app.get('/api/books', (req, res) => {
-    db.query('SELECT * FROM books', (err, results) => {
+    let query = `
+        SELECT 
+            b.id, 
+            b.title, 
+            c.name AS category, 
+            COUNT(p.id) AS count 
+        FROM 
+            books b 
+        LEFT JOIN 
+            categories c ON b.category_id = c.id 
+        LEFT JOIN 
+            poems p ON b.id = p.book_id 
+        GROUP BY 
+            b.id, b.title, c.name`;
+    const categoryId = req.query.category_id;
+    if (categoryId) {
+        query += ` WHERE b.category_id = ${categoryId}`;
+    }
+    db.query(query, (err, results) => {
         if (err) {
             return res.status(500).json({ error: err });
         }
@@ -46,43 +56,39 @@ app.get('/api/books', (req, res) => {
     });
 });
 
+// 获取诗词列表的 API
 app.get('/api/poems/:bookId', (req, res) => {
     const bookId = req.params.bookId;
     const query = 'SELECT * FROM poems WHERE book_id = ?';
     db.query(query, [bookId], (err, results) => {
-      if (err) return res.status(500).json({ error: err });
-      res.json(results);
+        if (err) return res.status(500).json({ error: err });
+        res.json(results);
     });
-  });
+});
 
-  app.get('/api/poem-contents/:poemId', (req, res) => {
+// 获取诗句内容的 API
+app.get('/api/poem-contents/:poemId', (req, res) => {
     const poemId = req.params.poemId;
     const query = 'SELECT content FROM poem_contents WHERE poem_id = ?';
     db.query(query, [poemId], (err, results) => {
-      if (err) return res.status(500).json({ error: err });
-      if (results.length === 0) return res.status(404).json({ message: 'Content not found' });
-      res.json(results[0]);
+        if (err) return res.status(500).json({ error: err });
+        if (results.length === 0) return res.status(404).json({ message: 'Content not found' });
+        res.json(results[0]);
     });
-  });
+});
 
-  app.get('/api/categories', (req, res) => {
-    const categories = ['书籍', '选集', '主题', '写景', '节日', '节气', '词牌', '时间', '时令', '花卉', '课本', '地理', '城市', '名山', '用典'];
-    res.json(categories);
-  });
+// 获取分类列表的 API
+app.get('/api/categories', (req, res) => {
+    const query = 'SELECT * FROM categories';
+    db.query(query, (err, results) => {
+        if (err) {
+            return res.status(500).json({ error: err });
+        }
+        res.json(results); // 返回分类数据
+    });
+});
 
-  app.get('/api/books', (req, res) => {
-    const books = [
-      { id: 1, title: '唐诗全集', category: '书籍', count: 200 },
-      { id: 2, title: '诗经全集', category: '选集', count: 305 },
-      { id: 3, title: '楚辞全集', category: '选集', count: 17 },
-      { id: 4, title: '道德经', category: '选集', count: 81 },
-      { id: 5, title: '时间简史', category: '主题', count: 150 },
-      // 其他书籍...
-    ];
-    res.json(books);
-  });
-  
 // 启动服务器
 app.listen(PORT, () => {
     console.log(`Server is running on http://localhost:${PORT}`);
-});
+});