Forráskód Böngészése

完成tab3界面的美化和设计

s202226701053 6 hónapja
szülő
commit
cacb9871ed

+ 5 - 0
E-Cover-app/src/app/app.routes.ts

@@ -31,4 +31,9 @@ export const routes: Routes = [
     loadComponent:()=>
       import('./user-info/user-info.component').then((m)=>m.UserInfoComponent),
   },
+  {
+    path:'sendPost',
+    loadComponent:()=>
+      import('./send-post/send-post.component').then((m)=>m.SendPostComponent),
+  },
 ];

+ 166 - 0
E-Cover-app/src/app/send-post/hwobs.service.ts

@@ -0,0 +1,166 @@
+import { Injectable } from '@angular/core';
+
+// @ts-ignore
+import ObsClient from "esdk-obs-browserjs"
+
+import Parse from "parse";
+
+/**
+ * HwobsDir 华为OBS目录接口
+ * @public
+ */
+export interface HwobsDir{
+  Prefix:string  // "storage/2023/"
+
+}
+
+/**
+ * HwobsDir 华为OBS文件接口
+ * @public
+ */
+export interface HwobsFile{
+  ETag: "\"f0ec968fe51ab48348307e06476122eb\""
+  Key:string  //"storage/3mkf41033623275.png"
+  LastModified:string //"2023-11-08T12:03:13.008Z"
+  Owner:object // {ID: '09971a1979800fb60fbbc00ada51f7e0'}
+  Size:string //"25839"
+  StorageClass:string //"STANDARD"
+}
+
+/**
+ * HwobsProvider 华为OBS文件服务
+ * @public
+ */
+export class HwobsProvider {
+  obsClient:ObsClient
+  bucketName:string
+  host:string
+  globalPrefix:string = ""
+  constructor(options:{
+    host:string
+    bucketName:string
+    access_key_id:string
+    secret_access_key:string
+    prefix?:string
+    server?:string
+  }) {
+      this.globalPrefix = options.prefix || ""
+      this.host = options?.host
+      this.bucketName = options?.bucketName
+      this.obsClient = new ObsClient({
+        access_key_id: options.access_key_id,       
+        secret_access_key: options.secret_access_key,
+        // 这里以华南-广州为例,其他地区请按实际情况填写
+        server: options?.server||'https://obs.cn-south-1.myhuaweicloud.com'
+    });
+  }
+
+  /**
+   * 目录及检索相关函数
+   */
+  listDir(prefix:any):Promise<{
+    dirs:Array<HwobsDir>,
+    files:Array<HwobsFile>
+  }>{
+    return new Promise((resolve,reject)=>{
+        this.obsClient.listObjects({
+          Bucket : this.bucketName,
+          Prefix : prefix,
+          Delimiter: '/'
+        }, (err:any, result:any) => {
+          if(err){              
+            console.error('Error-->' + err);   
+            reject(err)    
+          }else{              
+            console.log('Status-->' + result.CommonMsg.Status);
+            console.log(result)
+                    if(result.CommonMsg.Status < 300 && result.InterfaceResult){
+                          for(var j in result.InterfaceResult.Contents){
+                                    console.log('Contents[' + j +  ']:');
+                                    console.log('Key-->' + result.InterfaceResult.Contents[j]['Key']);
+                                    console.log('Owner[ID]-->' + result.InterfaceResult.Contents[j]['Owner']['ID']);
+                            }
+                    }       
+                    let dirs:HwobsDir[] = result.InterfaceResult.CommonPrefixes
+                    let files:HwobsFile[] = result.InterfaceResult.Contents
+                    resolve({dirs:dirs,files:files})     
+          }
+        });
+      })
+
+  }
+
+  /**
+   * 文件上传相关函数
+   * @param file 
+   * @param key 
+   * @returns 
+   */
+  async uploadFile(file:File,key:string):Promise<Parse.Object>{
+    console.log(this.globalPrefix,key)
+    // key 文件上传后的全部路径
+    // /storage/<公司账套>/<应用名称>/年月日/<文件名>.<文件后缀>
+    // /storage/web2023/<学号>/年月日/<文件名>.<文件后缀>
+    let attach = await this.checkFileExists(file);
+    if(attach?.id) return attach
+      return new Promise((resolve,reject)=>{
+        this.obsClient.putObject({
+              Bucket : this.bucketName,
+              Key : this.globalPrefix+key,
+              SourceFile : file
+        },  async (err:any, result:any) => {
+              if(err){
+                    console.error('Error-->' + err);
+                    reject(err)
+              }else{
+                console.log('Status-->' + result.CommonMsg.Status);
+                let attach = await this.saveAttachment(file,this.globalPrefix+key)
+                resolve(attach)
+              }
+        });
+      })
+  }
+  Attachment = Parse.Object.extend("Attachment")
+
+  async checkFileExists(file:any):Promise<Parse.Object>{
+    let hash = await this.getFileHash(file)
+    // 文件HASH查重,避免重复上传
+    let attach:Parse.Object
+    let query = new Parse.Query("Attachment")
+    query.equalTo("hash",hash);
+    query.equalTo("size",file.size);
+    let exists:any = await query.first();
+    if(!exists?.id) exists = new this.Attachment()
+    attach = exists
+    return attach
+  }
+  async saveAttachment(file:File,key:string){
+    console.log("saveAttachment",key)
+    let hash = await this.getFileHash(file)
+    let attach = await this.checkFileExists(file)
+    attach.set("name",file.name)
+    attach.set("size",file.size)
+    attach.set("mime",file.type)
+    attach.set("url",this.host + key)
+    attach.set("hash",hash)
+    attach = await attach.save()
+    return attach
+  }
+
+  async getFileHash(file:File) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = async (event:any) => {
+        const buffer = event.target.result;
+        const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
+        const hashArray = Array.from(new Uint8Array(hashBuffer));
+        const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
+        resolve(hashHex);
+      };
+      reader.onerror = (event:any) => {
+        reject(event.target.error);
+      };
+      reader.readAsArrayBuffer(file);
+    });
+  }
+}

+ 77 - 0
E-Cover-app/src/app/send-post/send-post.component.html

@@ -0,0 +1,77 @@
+<!--头部-->
+<app-custom-header [param]="['发布帖子','text','    ']"></app-custom-header>
+<ion-button shape="round" class="publish-button" (click)="publishPost()"
+  [ngClass]="{'active': inputText.length > 0 && remainingChars<20}"
+  [disabled]="inputText.length === 0 || remainingChars < 0">
+  发布
+</ion-button>
+<!--内容-->
+<ion-content class="content">
+  <div class="input-area">
+    <div class="title-input">
+      <ion-input [(ngModel)]="title" placeholder="添加标题" maxlength="20" (ionInput)="updateTitleLength()">
+      </ion-input>
+      <span class="char-counter">{{ remainingChars }} </span>
+    </div>
+    <!--如果用户选择了标签,则显示-->
+    <div *ngIf="tags.length > 0" id="show-tag">
+      <ion-button class="tags" shape="round" *ngFor="let tag of tags" (click)="removeTags(tag)">
+        <ion-img src="https://s1.imagehub.cc/images/2024/12/24/713f569886bd5eb876b5cf543777062b.png" />
+        {{ tag }}
+      </ion-button>
+    </div>
+    <ion-textarea [(ngModel)]="inputText" [autoGrow]="true" placeholder="分享你的穿搭...">
+    </ion-textarea>
+  </div>
+  <!--图片-->
+  <div class="image-box" *ngFor="let image of images; let i = index">
+    <img [src]="image" alt="Post Image" class="image">
+    <ion-icon name="close-circle-outline" class="delete-icon" (click)="removeImage(i)"></ion-icon>
+  </div>
+</ion-content>
+<!--标签-->
+<ion-content class="tag-container">
+  <ion-button class="tags" shape="round" *ngFor="let tag of ['标签1','标签2','标签3','标签4']" (click)="addTags(tag)">
+    <ion-img src="https://s1.imagehub.cc/images/2024/12/24/713f569886bd5eb876b5cf543777062b.png" />
+    {{ tag }}
+  </ion-button>
+</ion-content>
+
+
+<ion-footer class="footer">
+  <div class="left-icons">
+    <div class="footer-button" (click)="uploadImage()">
+      <ion-icon name="image-outline"></ion-icon>
+    </div>
+    <div fill="clear" class="footer-button">
+      <ion-icon name="at-outline"></ion-icon>
+    </div>
+    <div fill="clear" class="footer-button" (click)="openEmojiPicker()">
+      <ion-icon name="happy-outline"></ion-icon>
+    </div>
+  </div>
+  <div class="location-tag" (click)="removeLocationTag()" *ngIf="showLocationTag">
+
+    <ion-icon name="pin"></ion-icon>
+    <span>南昌市</span>
+    <ion-icon name="close-outline" slot="end"></ion-icon>
+
+  </div>
+  <!-- 隐藏的文件输入 -->
+  <input type="file" accept="image/*" (change)="onFileChange($event)" style="display: none;" #fileInput>
+
+  <!-- 表情模拟框 -->
+  <ion-modal [isOpen]="isEmojiPickerOpen" (didDismiss)="closeEmojiPicker()" [initialBreakpoint]="0.71"
+    [breakpoints]="[0, 0.25,0.50,0.71]" handleBehavior="cycle">
+    <ng-template>
+      <ion-content class="emoji-picker">
+        <div class="emoji-container">
+          <div *ngFor="let emoji of emojis" (click)="addEmoji(emoji)" class="emoji-button"> <!-- 表情按钮 -->
+            {{ emoji }}
+          </div>
+        </div>
+      </ion-content>
+    </ng-template>
+  </ion-modal>
+
+</ion-footer>

+ 197 - 0
E-Cover-app/src/app/send-post/send-post.component.scss

@@ -0,0 +1,197 @@
+.publish-button {
+    position: absolute;
+    z-index: 99999;
+    height: 50px;
+    width: 120px;
+    background-color: rgb(236, 235, 233);
+    color: gray;
+    font-size: 18px;
+    right: 10px;
+
+
+    &.active {
+        /* 当输入框有内容时的样式 */
+        background-color: white;
+        /* 背景色变为黑色 */
+        color: black;
+        /* 字体颜色变为白色 */
+    }
+}
+
+.content {
+    display: flex;
+    flex-direction: column;
+    /* 垂直排列子元素 */
+    height: 100%;
+    /* 确保内容区域占满整个高度 */
+}
+
+.input-area {
+    flex: 1;
+    /* 让输入区域占用剩余空间 */
+
+    padding: 10px;
+    /* 设置内边距 */
+    font-size: 18px;
+}
+
+.title-input {
+    display: flex;
+    /* 使用 flex 布局 */
+    justify-content: space-between;
+    /* 左右对齐 */
+    align-items: center;
+    /* 垂直居中对齐 */
+    margin-bottom: 10px;
+    /* 标题输入框与文本区域之间的间距 */
+}
+
+
+.title-input ion-input {
+    border: none;
+    /* 移除默认边框 */
+    border-bottom: 1px solid #ccc;
+    /* 添加下边框 */
+    flex: 1;
+    /* 使输入框填满可用空间 */
+    margin-right: 10px;
+    /* 输入框与字数计数器之间的间距 */
+}
+
+.char-counter {
+    color: gray;
+    /* 字数计数器的颜色 */
+    font-size: 16px;
+    /* 字数计数器的字体大小 */
+    white-space: nowrap;
+    /* 防止换行 */
+}
+
+.footer {
+    display: flex;
+    /* 使用 flex 布局 */
+    justify-content: space-between;
+    /* 左右对齐 */
+    align-items: center;
+    /* 垂直居中对齐 */
+    padding-left: 10px;
+    height: 60px;
+    /* 设置底部的高度 */
+    background-color: #f0f0f0;
+    /* 设置背景色 */
+}
+
+.left-icons {
+    display: flex;
+    /* 使用 flex 布局 */
+    align-items: center;
+    /* 垂直居中对齐 */
+}
+
+.location-tag {
+    display: flex;
+    /* 使用 flex 布局 */
+    justify-content: space-between;
+    /* 左右对齐 */
+    align-items: center;
+    /* 垂直居中对齐 */
+    background-color: white;
+    width: 110px;
+    height: 40px;
+    border-radius: 20px;
+    margin-right: 20px;
+    margin-top: 5px;
+    padding-right: 10px;
+}
+
+.footer-button {
+    color: black;
+    font-size: 40px;
+    margin-right: 20px;
+    margin-top: 30px;
+    margin-bottom: 10px;
+}
+
+/* 表情选择器样式 */
+.emoji-picker {
+    --background: transparent;
+    /* 去除默认样式 */
+    background-color: white;
+    /* 背景颜色 */
+    padding: 10px;
+    display: flex;
+    justify-content: center;
+    /* 水平居中 */
+    overflow: hidden;
+    /* 隐藏多余内容 */
+}
+
+/* 表情容器用于支持滚动 */
+.emoji-container {
+    display: flex;
+    flex-wrap: wrap;
+    /* 允许换行 */
+    overflow-y: auto;
+    /* 允许纵向滚动 */
+    max-height: 70vh;
+    /* 最大高度,防止超出屏幕 */
+    width: 100%;
+    /* 容器宽度 */
+}
+
+/* 表情按钮 */
+.emoji-button {
+    margin: 5px;
+    /* 每个表情与顶部的间距 */
+    font-size: 28px;
+    /* 字体大小 */
+    height: 40px;
+    /* 按钮高度 */
+    width: 40px;
+    /* 按钮宽度 */
+    display: flex;
+    /* 使用 flexbox 对齐 */
+    align-items: center;
+    /* 垂直居中 */
+    justify-content: center;
+    /* 水平居中 */
+    --background: transparent;
+    /* 背景透明 */
+    --box-shadow: none;
+    /* 去掉阴影 */
+    --outline: none;
+    /* 去掉轮廓 */
+    border: none;
+    /* 去掉边框 */
+}
+
+//标签
+.tag-container {
+    display: flex;
+    justify-content: flex-start;
+    margin-bottom: 10px;
+    height: 10px;
+    bottom: 10px;
+}
+
+.tags {
+    --background: rgba(94, 94, 94, 0.503);
+    --padding-start: 5px;
+    --padding-end: 5px;
+    font-size: 17px;
+
+    ion-img {
+        width: 25px;
+        height: 25px;
+    }
+}
+
+.tagpicture {
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+
+.posts-container {
+    width: 100%; // 宽度占满父元素
+}

+ 22 - 0
E-Cover-app/src/app/send-post/send-post.component.spec.ts

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

+ 183 - 0
E-Cover-app/src/app/send-post/send-post.component.ts

@@ -0,0 +1,183 @@
+import { CommonModule } from '@angular/common';
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { NavController } from '@ionic/angular';
+import { IonButton, IonButtons, IonCardContent, IonContent, IonFooter, IonHeader, IonIcon, IonImg, IonInfiniteScrollContent, IonInput, IonItem, IonTextarea, IonTitle, IonToolbar, } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { atOutline, bookmarkOutline, chevronBackSharp, closeOutline, happyOutline, imageOutline, } from 'ionicons/icons';
+import { CloudObject, CloudUser } from 'src/lib/ncloud';
+import { IonModal, } from '@ionic/angular/standalone'; // 导入独立组件
+import { HwobsProvider } from './hwobs.service';
+import { CustomHeaderComponent } from 'src/lib/component/custom-header/custom-header.component';
+
+addIcons({ chevronBackSharp, imageOutline, atOutline, happyOutline, closeOutline, bookmarkOutline });
+@Component({
+  selector: 'app-send-post',
+  templateUrl: './send-post.component.html',
+  styleUrls: ['./send-post.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonItem,
+    IonButton, IonIcon, IonButtons, IonInput, IonFooter, CommonModule,
+    FormsModule, IonTextarea, IonModal, CustomHeaderComponent, IonImg, IonInfiniteScrollContent],
+})
+export class SendPostComponent implements OnInit {
+  inputText: string = ''; // 用于绑定输入框的内容
+  title: string = ''; // 帖子标题
+  showLocationTag: boolean = true; // 控制标签是否显示
+  remainingChars: number = 20; // 剩余字符数
+  images: string[] = []; // 存储图片的数组
+  tags: string[] = []; // 存储标签的数组
+  isPost: boolean = false; // 帖子发布状态
+  @Input() url: string = "";
+  @Output() onUrlChange: EventEmitter<string> = new EventEmitter<string>()
+  uploader: HwobsProvider | undefined
+  ngOnInit() {
+    this.uploader = new HwobsProvider({
+      bucketName: "nova-cloud",
+      prefix: "dev/jxnu/storage/",
+      host: "https://app.fmode.cn/",
+      access_key_id: "XSUWJSVMZNHLWFAINRZ1",
+      secret_access_key: "P4TyfwfDovVNqz08tI1IXoLWXyEOSTKJRVlsGcV6"
+    });
+  }
+  constructor(private router: Router, private navCtrl: NavController) { }
+  goBack() {
+    this.navCtrl.back(); // 返回上一页
+  }
+  /**
+   * @发帖操作函数
+   * 
+   */
+  // 添加标签
+  addTags(tag: string) {
+    this.tags.push(tag); // 添加标签到 tags 数组中
+    console.log(this.tags)
+  }
+  // 删除标签
+  removeTags(tag: string) {
+    this.tags = this.tags.filter((item) => item !== tag); // 从 tags 数组中删除指定标签
+    console.log(this.tags)
+  }
+  // 添加图片
+  uploadImage() {
+    const fileInput = document.querySelector('input[type=file]') as HTMLInputElement;
+    if (fileInput) {
+      fileInput.click(); // 点击文件输入框
+    }
+  }
+  //删除上传的图片
+  removeImage(index: number) {//删除图片
+    this.images.splice(index, 1); // 从数组中删除指定索引的图片
+  }
+  // 发布帖子
+  publishPost() {
+    this.isPost = true;
+    const post = new CloudObject('post');
+    //设置帖子默认属性
+    post.set({
+      UserID: new CloudUser().toPointer(),
+      content: this.inputText,
+      title: this.title,
+      image: this.images,
+      tag: this.tags,
+      like: 0,
+    });
+    // 保存帖子到数据库
+    post.save()
+      .then(() => {
+        console.log('帖子已发布');
+        // 清空输入框和其他状态
+        this.inputText = '';
+        this.title = '';
+        this.images = [];
+        this.tags = [];
+      })
+      .catch((error: any) => {
+        console.error('发布帖子时出错:', error);
+      });
+    this.isPost = true; // 发布成功后,将 isPost 设置为 true
+    this.goBack();
+  }
+
+  /**
+   * @内置操作函数
+   */
+  //  删除标签
+  removeLocationTag() {
+    this.showLocationTag = false; // 设置为 false 隐藏标签
+  }
+  //  更新标题长度
+  updateTitleLength() {
+    this.remainingChars = 20 - this.title.length; // 更新剩余字符数
+  }
+
+  file: File | undefined
+  fileData: any = ""
+  fileList: File[] = []
+  // 上传文件
+  async upload() {
+    let filename = this.file?.name;
+    let dateStr = `${new Date().getFullYear()}${new Date().getMonth() + 1}${new Date().getDate()}`;
+    let hourStr = `${new Date().getHours()}${new Date().getMinutes() + 1}${new Date().getSeconds()}`;
+    let key = `${dateStr}/${hourStr}-${filename}`;
+    // let key = `storage/${filename}`
+    if (this.file) {
+      let attachment = await this.uploader?.uploadFile(this.file, key);
+      console.log(attachment);
+      this.url = attachment?.get("url");
+      console.log(this.url);
+      this.onUrlChange.emit(this.url);
+      console.log(this.url);
+      this.images.push(this.url);
+    }
+  }
+  /**
+   * 文件选择器 选择文件触发事件
+   * @param event 
+   */
+  async onFileChange(event: any) {
+    console.log(event)
+    // 将选择的文件列表,赋值给fileList
+    this.fileList = event?.target?.files;
+    // 默认将第一个文件,显示在展示区域
+    this.setFile(event?.target?.files?.[0]);
+    this.upload();
+    this.fileList = [];
+  }
+
+  /**
+   * 设置展示区域文件
+   * @param file 
+   */
+  async setFile(file: any) {
+    // 将文件设置为展示区域文件
+    this.file = file
+  }
+
+  isEmojiPickerOpen: boolean = false; // 控制表情选择器的打开状态
+  emojis: string[] = ['😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃', '🫠', '😉', '😊', '😇', '🥰', '😍', '🤩', '😘',
+    '😗', '☺️', '😚', '😙', '🥲', '😋', '😛', '😜', '🤪', '😝', '🤑', '🤗', '🤭', '🫢', '🫣', '🤫', '🤔', '🫡', '🤐', '🤨', '😐',
+    '😑', '😶', '🫥', '😶‍🌫️', '😏', '😒', '🙄', '😬', '😮‍💨', '🤥', '🫨', '🙂‍↔️', '🙂‍↕️', '😌', '😔', '😪', '🤤', '😴', '🫩', '😷',
+    '🤒', '🤕', '🤢', '🤮', '🤧', '🥵', '🥶', '🥴', '😵', '😵‍💫', '🤯', '🤠', '🥳', '🥸', '😎', '🤓', '🧐', '😕', '🫤', '😟',
+    '🙁', '☹️', '😮', '😯', '😲', '😳', '🥺', '🥹', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱', '😖', '😣', '😞', '😓',
+    '😩', '😫', '🥱', '😤', '😡', '😠', '🤬', '😈', '👿', '💀', '☠️', '💩', '🤡', '👹', '👺', '👻', '👽', '👾', '🤖', '😺',
+    '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾', '🙈', '🙉', '🙊', '💌', '💘', '❤️', '🖤', '💋', '💯', '💢', '💥', '💫',
+    '💦', '💤']; // 表情数组
+  // 打开表情选择器
+  openEmojiPicker() {
+    this.isEmojiPickerOpen = true;
+  }
+
+  // 关闭表情选择器
+  closeEmojiPicker() {
+    this.isEmojiPickerOpen = false; // 关闭模态框
+  }
+
+  // 添加表情到输入框
+  addEmoji(emoji: string) {
+    this.inputText += emoji; // 将选中的表情添加到输入框
+
+  }
+
+}

+ 3 - 6
E-Cover-app/src/app/tab2/tab2.page.html

@@ -1,11 +1,8 @@
 <!--头部,包括三个段-->
 
 <ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Tab 2</ion-title>
-    </ion-toolbar>
-  </ion-header>
+      <ion-button size="large" (click)="goSendPost()">Tab 2</ion-button>
 
-  <app-explore-container name="Tab 2 page"></app-explore-container>
+
+  
 </ion-content>

+ 189 - 0
E-Cover-app/src/app/tab2/tab2.page.scss

@@ -0,0 +1,189 @@
+//头部区域
+.head{
+    height:70px;
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    align-items: center; 
+
+}
+
+//发布按钮
+.publish-button {
+    height: 35px;
+    width: 70px;
+    background-color: rgb(236, 235, 233); /* 橙色背景 */
+    color: gray ; /* 强制设置文字颜色为白色 */
+    border-radius: 20px; /* 椭圆形 */
+    font-size: 16px; /* 调整字体大小 */
+
+  
+    &.active {
+      /* 当输入框有内容时的样式 */
+      background-color: white; /* 背景色变为黑色 */
+      color: black; /* 字体颜色变为白色 */
+    }
+  }
+  
+  .content {
+    display: flex;
+    flex-direction: column; /* 垂直排列子元素 */
+    height: 100%; /* 确保内容区域占满整个高度 */
+  }
+
+  .input-area {
+    flex: 1; /* 让输入区域占用剩余空间 */
+    
+    padding: 10px; /* 设置内边距 */
+    font-size: 18px;
+  }
+  
+  .title-input {
+    display: flex; /* 使用 flex 布局 */
+    justify-content: space-between; /* 左右对齐 */
+    align-items: center; /* 垂直居中对齐 */
+    margin-bottom: 10px; /* 标题输入框与文本区域之间的间距 */
+  }
+
+  
+  .title-input ion-input {
+    border: none; /* 移除默认边框 */
+    border-bottom: 1px solid #ccc; /* 添加下边框 */
+    flex: 1; /* 使输入框填满可用空间 */
+    margin-right: 10px; /* 输入框与字数计数器之间的间距 */
+  }
+
+ 
+
+  
+  .char-counter {
+    color: gray; /* 字数计数器的颜色 */
+    font-size: 16px; /* 字数计数器的字体大小 */
+    white-space: nowrap; /* 防止换行 */
+  }
+  
+  .footer {
+    display: flex; /* 使用 flex 布局 */
+    justify-content: space-between; /* 左右对齐 */
+    align-items: center; /* 垂直居中对齐 */
+    padding-left: 10px;
+    height: 60px; /* 设置底部的高度 */
+    background-color:#f0f0f0; /* 设置背景色 */
+  }
+  
+  .left-icons {
+    display: flex; /* 使用 flex 布局 */
+    align-items: center; /* 垂直居中对齐 */
+  }
+  
+  .location-tag {
+    display: flex; /* 使用 flex 布局 */
+    justify-content: space-between; /* 左右对齐 */
+    align-items: center; /* 垂直居中对齐 */
+    background-color: white;
+    width: 110px;
+    height: 40px;
+    border-radius: 20px;
+    margin-right: 20px;
+    margin-top: 5px;
+    padding-right: 10px;
+  }
+
+  .footer-button{
+    color:black; 
+    font-size: 40px;
+    margin-right: 20px;
+    margin-top: 30px;
+    margin-bottom: 10px;
+  }
+
+
+  /* 表情选择器样式 */
+.emoji-picker {
+  --background: transparent; /* 去除默认样式 */
+  background-color:white; /* 背景颜色 */
+  padding: 10px;
+  display: flex;
+  justify-content: center; /* 水平居中 */
+  overflow: hidden; /* 隐藏多余内容 */
+}
+
+/* 表情容器用于支持滚动 */
+.emoji-container {
+  display: flex;
+  flex-wrap: wrap; /* 允许换行 */
+  overflow-y: auto; /* 允许纵向滚动 */
+  max-height: 70vh; /* 最大高度,防止超出屏幕 */
+  width: 100%; /* 容器宽度 */
+}
+
+/* 表情按钮 */
+.emoji-button {
+  margin: 5px; /* 每个表情与顶部的间距 */
+  font-size: 28px; /* 字体大小 */
+  height: 40px; /* 按钮高度 */
+  width: 40px; /* 按钮宽度 */
+  display: flex; /* 使用 flexbox 对齐 */
+  align-items: center; /* 垂直居中 */
+  justify-content: center; /* 水平居中 */
+  --background: transparent; /* 背景透明 */
+  --box-shadow: none; /* 去掉阴影 */
+  --outline: none; /* 去掉轮廓 */
+  border: none; /* 去掉边框 */
+}
+//标签
+.tag-container {
+  display: flex;
+  justify-content: flex-start; /* 标签靠左对齐 */
+  padding: 10px; /* 标签容器内边距 */
+  margin-bottom: 10px; /* 将标签容器推到底部 */
+}
+
+.tag {
+  background-color: #f0f0f0; // 灰色背景
+  color: black; // 黑色字体
+  border-radius: 20px; // 椭圆形效果
+  padding: 5px 10px; // 标签内边距
+  margin-right: 17px; // 标签之间的间距
+  font-size: 14px; // 标签字体大小
+  display: inline-flex; // 让标签内容居中
+  align-items: center; // 垂直居中
+}
+
+.tagpicture{
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
+
+.posts-container {
+  width: 100%; // 宽度占满父元素
+}
+
+.post {
+  background-color: #f9f9f9; // 背景色
+  border: 1px solid #ddd; // 边框
+  border-radius: 8px; // 圆角
+  padding: 10px;
+  margin-bottom: 10px; // 帖子之间的间距
+}
+
+.post h3 {
+  margin: 0; // 去掉默认的 margin
+}
+
+.post p {
+  margin: 5px 0; // 设置段落的 margin
+}
+
+.image-gallery {
+  display: flex; // 使用 flexbox 来排列图片
+  flex-wrap: wrap; // 允许换行
+}
+
+.post-image {
+  width: 100px; // 设置图片的宽度
+  height: 100px; // 设置图片的高度
+  object-fit: cover; // 保持图片的比例
+  margin-right: 5px; // 图片之间的间距
+}

+ 7 - 5
E-Cover-app/src/app/tab2/tab2.page.ts

@@ -1,16 +1,18 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
+import { Router } from '@angular/router';
 
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
   styleUrls: ['tab2.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,IonButton]
 })
 export class Tab2Page {
-
-  constructor() {}
-
+  constructor(private router:Router) {}
+  goSendPost() {
+    this.router.navigate(['/sendPost']);
+  }
 }

+ 27 - 6
E-Cover-app/src/app/tab3/tab3.page.html

@@ -1,5 +1,7 @@
 <ion-content>
+  <!--个人资料版图-->
   <div id="banner">
+    <!--头部三个按钮-->
     <div id="header">
       <ion-button shape="round">
         <ion-icon slot="start" name="person-add-outline"></ion-icon>我的名片
@@ -7,21 +9,26 @@
       <ion-icon name="card-outline" size="large"></ion-icon>
       <ion-text (click)="goSetting()">设置</ion-text>
     </div>
+    <!--个人资料-->
     <div id="personInfo">
       <div id="avatarAndName">
+        <!--头像-->
         <img id="avatar" [src]="currentUser?.get('avatar') ||'assets/icon/default_avatar.jpg'" alt="头像" />
+        <!--用户名和标签-->
         <div id="nameAndTags">
           <h1>{{currentUser?.get('name')}}</h1>
           <ion-card class="tags">社交恐惧症</ion-card>
           <ion-card class="tags">穿衣有肉</ion-card>
         </div>
       </div>
+      <!--签名-->
       @if(currentUser?.get('signature')){
       <p>{{ currentUser?.get('signature') }}</p>
       }
       @else{
       <p>这家伙很懒,什么都没有留下</p>
       }
+      <!--统计-->
       <div id="count">
         <h3 style="display: inline;">{{ currentUser?.get('fans') }}</h3>
         <p style="display: inline;">粉丝</p>
@@ -30,7 +37,7 @@
         <h3 style="display: inline;">{{ formatNumber(currentUser?.get('like')) }}</h3>
         <p style="display: inline;">获赞</p>
       </div>
-
+      <!--勋章-->
       <ion-button id="badge">
         <ion-icon slot="start" name="flame-outline"></ion-icon>
         我的勋章
@@ -38,7 +45,8 @@
       </ion-button>
     </div>
   </div>
-  <ion-card style="--background:#f8f8f8">
+  <!--统计版图-->
+  <ion-card style="--background:white;box-shadow: none;">
     <div class="card">
       <p>赞过帖子</p>
       <p>11</p>
@@ -64,9 +72,22 @@
         <ion-label>动态</ion-label>
       </ion-segment-button>
     </ion-segment>
-    <ion-segment-view>
-      <ion-segment-content id="myPost">First</ion-segment-content>
-      <ion-segment-content id="news">Second</ion-segment-content>
-    </ion-segment-view>
   </ion-card>
+  <ion-segment-view>
+    <ion-segment-content id="myPost">
+      <ion-card class="post" *ngFor="let post of posts">
+        <ion-img [src]="post?.get('image')[0] || 'assets/icon/favicon.png'" />
+        <ion-title>{{post?.get('title')}}</ion-title>
+        <div id="author">
+          <ion-avatar><img [src]="currentUser?.get('avatar') || 'assets/icon/default_avatar.jpg'" /></ion-avatar>
+          <ion-label>{{currentUser?.get('name')}}</ion-label>
+        </div>
+      </ion-card>
+      <p id="footer">我是有底线的</p>
+    </ion-segment-content>
+
+    <ion-segment-content id="news">
+
+    </ion-segment-content>
+  </ion-segment-view>
 </ion-content>

+ 71 - 24
E-Cover-app/src/app/tab3/tab3.page.scss

@@ -1,17 +1,9 @@
-ion-content {
-    --background: #000000;
-    color: black;
+#banner {
+    background: rgb(25, 25, 25);
+}
 
-    ion-header {
-        background: #f8f8f8;
-        color: black;
-        text-align: center;
-        box-shadow: none;
-        padding: 10px;
-        font-size: large;
-        display: none;
-        position: absolute;
-    }
+ion-content {
+    --background: black;
 
     ion-card {
         width: 100%;
@@ -24,24 +16,24 @@ ion-content {
             margin: 0;
         }
 
-        ion-segment{
+        ion-segment {
             width: 40%;
         }
-        ion-segment-button{
+
+        ion-segment-button {
             font-size: medium;
-            --color:gray;
-            --color-checked:black;
-            --indicator-color:orange;
+            --color: gray;
+            --color-checked: black;
+            --indicator-color: orange;
             --indicator-height: 5px;
         }
-        ion-segment-view{
-            color: #000000;
-        }
+
     }
-}
 
-#banner {
-    background: rgb(36, 36, 36);
+    ion-segment-view {
+        color: #000000;
+        height: auto;
+    }
 }
 
 #header {
@@ -79,6 +71,7 @@ ion-content {
     border: #414040 1px solid;
     padding: 0 5px;
     margin: 0 2px;
+    color: gray !important;
 }
 
 #avatarAndName {
@@ -99,6 +92,7 @@ ion-content {
 
 #nameAndTags h1 {
     margin: 5px;
+    color: white;
 }
 
 #personInfo p {
@@ -109,6 +103,8 @@ ion-content {
     display: flex;
     align-items: center;
     justify-content: start;
+    color: white;
+    box-shadow: none;
 }
 
 #count h3 {
@@ -135,4 +131,55 @@ ion-content {
     margin: 15px 2%;
     border-radius: 10px;
     padding: 10px;
+}
+
+#myPost {
+    width: 100%;
+    background: rgb(247, 247, 247);
+    color: black;
+    height: fit-content;
+    column-count: 2;
+    column-gap: 20px; // 设置列之间的间距
+
+    ion-card {
+        // width: 46%;
+        // margin: 2%;
+        // float: left;
+        box-shadow: none;
+        --background: white;
+        display: inline-block;
+        width: 100%;
+        margin-bottom: 20px; // 添加底部外边距以避免帖子紧贴在一起
+        break-inside: avoid; // 确保帖子不会被分割到两列中
+
+        ion-title {
+            margin: 0;
+            padding: 10px 2px;
+            color: black;
+            font-size: 15px;
+        }
+
+        #author {
+            display: flex;
+            align-items: center;
+            margin: 10px 2px;
+
+            ion-avatar {
+                width: 25px;
+                height: 25px;
+                border-radius: 1000px;
+            }
+
+            ion-label {
+                color: gray;
+            }
+        }
+    }
+
+    p {
+        clear: both;
+        color: gray;
+        font-size: 15px;
+        text-align: center;
+    }
 }

+ 19 - 12
E-Cover-app/src/app/tab3/tab3.page.ts

@@ -1,22 +1,23 @@
 import { Component } from '@angular/core';
 import { IonicModule } from '@ionic/angular';
 import { ModalController } from '@ionic/angular/standalone';
-import { CloudUser, CloudObject } from 'src/lib/ncloud';
+import { CloudUser, CloudObject, CloudQuery } from 'src/lib/ncloud';
 import { Router } from '@angular/router';
 import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
 import { addIcons } from 'ionicons';
 import { cardOutline, chevronForwardOutline, flameOutline, personAddOutline } from 'ionicons/icons';
-addIcons({ personAddOutline, cardOutline,flameOutline,chevronForwardOutline })
+import { CommonModule } from '@angular/common';
+addIcons({ personAddOutline, cardOutline, flameOutline, chevronForwardOutline })
 @Component({
   selector: 'app-tab3',
   templateUrl: 'tab3.page.html',
   styleUrls: ['tab3.page.scss'],
   standalone: true,
-  imports: [IonicModule],
+  imports: [IonicModule,CommonModule],
 })
 export class Tab3Page {
   currentUser: CloudUser | undefined
-  constructor(private modalCtrl: ModalController, private router: Router) { }
+  constructor(private modalCtrl: ModalController, private router: Router) {}
   // 格式化数字的方法
   formatNumber(num: number): string {
     if (num >= 10000) {
@@ -24,7 +25,7 @@ export class Tab3Page {
     } else if (num >= 1000) {
       return (num / 1000).toFixed(1) + 'k';
     } else {
-      return num+'';
+      return num + '';
     }
   }
   async ionViewWillEnter() {
@@ -45,6 +46,7 @@ export class Tab3Page {
     }
     console.log("进入到我的界面,准备更新数据");
     this.currentUser = new CloudUser();
+    this.getPosts();
   }
 
   async signup() {
@@ -54,13 +56,18 @@ export class Tab3Page {
     // this.currentUser = user;
     //}
   }
-  logout() {
-    this.currentUser?.logout();
-  }
-  editUser() {
-
-  }
-  goSetting(){
+  goSetting() {
     this.router.navigate(["/settings"]);
   }
+  /**
+   * @读取用户发布的帖子
+   */
+  posts: any[] | undefined;
+  async getPosts() {
+    let query = new CloudQuery('post');
+    query.equalTo('UserID', this.currentUser?.toPointer());
+    this.posts = await query.find();
+    console.log("获取到用户发布的帖子:");
+    console.log(this.posts);
+  }
 }

BIN
E-Cover-app/src/assets/icon/favicon.png


+ 2 - 1
E-Cover-app/src/lib/component/custom-header/custom-header.component.scss

@@ -25,7 +25,8 @@ ion-header {
 
     font-family: 'header-font';
     top: 24px;
-    width: 100%;
+    left:0;
+    width: 100vw;
     text-align: center;
     margin:0 0;
 }