Browse Source

个人主页

warrior 3 days ago
parent
commit
783ebe84c5
24 changed files with 1087 additions and 102 deletions
  1. 12 0
      projects/live-app/src/app/components/image-preview/image-preview.component.html
  2. 32 0
      projects/live-app/src/app/components/image-preview/image-preview.component.scss
  3. 28 0
      projects/live-app/src/app/components/image-preview/image-preview.component.spec.ts
  4. 68 0
      projects/live-app/src/app/components/image-preview/image-preview.component.ts
  5. 39 22
      projects/live-app/src/moduls/tabs/home/home.component.html
  6. 9 0
      projects/live-app/src/moduls/tabs/home/home.component.scss
  7. 83 24
      projects/live-app/src/moduls/tabs/home/home.component.ts
  8. 4 4
      projects/live-app/src/moduls/tabs/my/my.component.html
  9. 52 8
      projects/live-app/src/moduls/tabs/my/my.component.ts
  10. 10 9
      projects/live-app/src/moduls/user/album/album.component.ts
  11. 58 12
      projects/live-app/src/moduls/user/profile/profile.component.html
  12. 103 16
      projects/live-app/src/moduls/user/profile/profile.component.scss
  13. 14 4
      projects/live-app/src/moduls/user/profile/profile.component.ts
  14. 87 0
      projects/live-app/src/moduls/user/setting/setting.component.html
  15. 67 0
      projects/live-app/src/moduls/user/setting/setting.component.scss
  16. 28 0
      projects/live-app/src/moduls/user/setting/setting.component.spec.ts
  17. 156 0
      projects/live-app/src/moduls/user/setting/setting.component.ts
  18. 24 0
      projects/live-app/src/moduls/user/share/share.component.html
  19. 45 0
      projects/live-app/src/moduls/user/share/share.component.scss
  20. 28 0
      projects/live-app/src/moduls/user/share/share.component.spec.ts
  21. 116 0
      projects/live-app/src/moduls/user/share/share.component.ts
  22. 11 0
      projects/live-app/src/moduls/user/user.modules.routes.ts
  23. 13 0
      projects/live-app/src/services/aichart.service.ts
  24. 0 3
      projects/live-app/src/services/auth.service.ts

+ 12 - 0
projects/live-app/src/app/components/image-preview/image-preview.component.html

@@ -0,0 +1,12 @@
+<div
+  id="image-container"
+  class="image-container"
+  [style.display]="show"
+  (click)="close()"
+>
+  <img
+    [src]="image"
+    [ngStyle]="{ transform: getTransformStyle() }"
+    alt="Image Preview"
+  />
+</div>

+ 32 - 0
projects/live-app/src/app/components/image-preview/image-preview.component.scss

@@ -0,0 +1,32 @@
+.image-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  background: #000000d4;
+  z-index: 11;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  touch-action: none; /* 禁用默认触摸行为 */
+}
+
+.image-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  touch-action: none; /* 禁用默认触摸行为 */
+}
+
+img {
+  max-width: 100%;
+  max-height: 100%;
+  transition: transform 0.1s ease-out;
+}

+ 28 - 0
projects/live-app/src/app/components/image-preview/image-preview.component.spec.ts

@@ -0,0 +1,28 @@
+/* tslint:disable:no-unused-variable */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+import { ImagePreviewComponent } from './image-preview.component';
+
+describe('ImagePreviewComponent', () => {
+  let component: ImagePreviewComponent;
+  let fixture: ComponentFixture<ImagePreviewComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ImagePreviewComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ImagePreviewComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 68 - 0
projects/live-app/src/app/components/image-preview/image-preview.component.ts

@@ -0,0 +1,68 @@
+import { CommonModule } from '@angular/common';
+import { Component, Input, OnInit, AfterViewInit, ElementRef, ViewChild, ChangeDetectorRef } from '@angular/core';
+import { GestureController, Gesture, IonicModule } from '@ionic/angular';
+
+@Component({
+  selector: 'app-image-preview',
+  templateUrl: './image-preview.component.html',
+  styleUrls: ['./image-preview.component.scss'],
+  standalone: true,
+  imports: [IonicModule, CommonModule],
+})
+export class ImagePreviewComponent implements OnInit, AfterViewInit {
+  @Input() image: string = '';
+  show: string = 'none';
+  private gesture: Gesture | undefined;
+  private scale = 1;
+  private startX = 0;
+  private startY = 0;
+  private currentX = 0;
+  private currentY = 0;
+
+  // @ViewChild('image-container', { static: true }) imageContainer!: ElementRef;
+
+  constructor(private gestureCtrl: GestureController,private cdRef: ChangeDetectorRef) {}
+
+  ngOnInit(): void {
+    // 初始化手势
+  }
+
+  ngAfterViewInit(): void {
+    this.initGesture();
+  }
+
+  initGesture(): void {
+    let doc:any = document.getElementById('image-container')
+    this.gesture = this.gestureCtrl.create({
+      el: doc,
+      gestureName: 'pinch-zoom',
+      threshold: 0,
+      onStart: (_: any) => {
+        this.startX = this.currentX;
+        this.startY = this.currentY;
+      },
+      onMove: (ev: any) => {
+       if(ev.scale) this.scale = Math.max(1, Math.min(3, ev.scale));
+        this.currentX = this.startX + ev.deltaX;
+        this.currentY = this.startY + ev.deltaY;
+        this.cdRef.detectChanges();
+      },
+      onEnd: () => {
+        if (this.scale < 1.1) {
+          this.scale = 1;
+          this.currentX = 0;
+          this.currentY = 0;
+        }
+        console.log(this.getTransformStyle());
+      }
+    });
+    this.gesture.enable();
+  }
+
+  getTransformStyle(): string {
+    return `scale(${this.scale}) translateX(${this.currentX}px) translateY(${this.currentY}px)`;
+  }
+  close(){
+    this.show = 'none'
+  }
+}

+ 39 - 22
projects/live-app/src/moduls/tabs/home/home.component.html

@@ -5,30 +5,17 @@
         [scrollable]="true"
         (ionChange)="segmentChanged($event)"
         layout="icon-bottom"
-        value="recommend"
+        [value]="currentValue"
         mode="md"
       >
-        <ion-segment-button value="recommend" class="tabs">
-          <ion-label>关注</ion-label>
-        </ion-segment-button>
-        <ion-segment-button value="store" class="tabs">
-          <ion-label>推荐</ion-label>
-        </ion-segment-button>
-        <ion-segment-button value="device" class="tabs">
-          <ion-label>新人</ion-label>
-        </ion-segment-button>
-        <ion-segment-button value="star3" class="tabs">
-          <ion-label>三星</ion-label>
-        </ion-segment-button>
-        <ion-segment-button value="star4" class="tabs">
-          <ion-label>四星</ion-label>
-        </ion-segment-button>
-        <ion-segment-button value="star6" class="tabs">
-          <ion-label>五星</ion-label>
+        @for (item of options; track $index) {
+        <ion-segment-button [value]="item?.value" class="tabs">
+          <ion-label>{{ item?.label }}</ion-label>
         </ion-segment-button>
+        }
       </ion-segment>
       <div class="more">
-        <img src="/img/more.png" alt="" />
+        <img (click)="isOpen = true" src="/img/more.png" alt="" />
         <img src="/img/search.png" alt="" (click)="search()" />
       </div>
     </div>
@@ -61,8 +48,9 @@
       <div class="title-tag">在线聊天</div>
       <div class="live-tag">
         <!-- <span></span>  -->
-        <img src="/img/live.gif" alt="">
-        直播中</div>
+        <img src="/img/live.gif" alt="" />
+        直播中
+      </div>
       <div class="room-footer">
         <div class="user">
           {{
@@ -76,7 +64,7 @@
             <ion-icon name="location-outline"></ion-icon>
             江西
           </div>
-          <div class="row-li" style="margin-left: 4px;">
+          <div class="row-li" style="margin-left: 4px">
             <img
               src="https://file-cloud.fmode.cn/Qje9D4bqol/20241109/pctmvt110807052.png"
               alt=""
@@ -89,3 +77,32 @@
     }
   </div>
 </ion-content>
+<ion-modal
+  #modal
+  trigger="open-modal"
+  [isOpen]="isOpen"
+  (didDismiss)="onDidDismiss($event)"
+>
+  <ng-template>
+    <ion-toolbar>
+      <ion-buttons slot="start">
+        <ion-button (click)="cancel('cancel')">取消</ion-button>
+      </ion-buttons>
+      <ion-buttons slot="end">
+        <ion-button (click)="cancel('confirm', currentValue)">确认</ion-button>
+      </ion-buttons>
+    </ion-toolbar>
+    <ion-picker>
+      <ion-picker-column
+        [value]="currentValue"
+        (ionChange)="onIonChange($event)"
+      >
+        @for (item of options; track $index) {
+        <ion-picker-column-option value="{{ item?.value }}">{{
+          item?.label
+        }}</ion-picker-column-option>
+        }
+      </ion-picker-column>
+    </ion-picker>
+  </ng-template>
+</ion-modal>

+ 9 - 0
projects/live-app/src/moduls/tabs/home/home.component.scss

@@ -148,3 +148,12 @@
     }
   }
 }
+ion-modal {
+  --height: auto;
+
+  align-items: end;
+}
+
+ion-picker {
+  margin-bottom: var(--ion-safe-area-bottom);
+}

+ 83 - 24
projects/live-app/src/moduls/tabs/home/home.component.ts

@@ -1,4 +1,9 @@
-import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
+import {
+  Component,
+  CUSTOM_ELEMENTS_SCHEMA,
+  OnInit,
+  ViewChild,
+} from '@angular/core';
 import * as Parse from 'parse';
 import { IonicModule } from '@ionic/angular';
 import { Router } from '@angular/router';
@@ -14,9 +19,49 @@ import { Swiper } from 'swiper';
   // schemas: [CUSTOM_ELEMENTS_SCHEMA],
 })
 export class HomeComponent implements OnInit {
-  active: string = 'recommend';
+  @ViewChild('modal') modal!: any;
+  options: Array<any> = [
+    {
+      label: '关注',
+      value: 'follow',
+      icon: 'home-outline',
+      color: 'primary',
+    },
+    {
+      label: '推荐',
+      value: 'recommend',
+      icon: 'home-outline',
+      color: 'primary',
+    },
+    {
+      label: '新人',
+      value: 'news',
+      icon: 'videocam-outline',
+      color: 'danger',
+    },
+    {
+      label: '三星',
+      value: 'star3',
+      icon: 'videocam-outline',
+      color: 'danger',
+    },
+    {
+      label: '四星',
+      value: 'star4',
+      icon: 'videocam-outline',
+      color: 'danger',
+    },
+    {
+      label: '五星',
+      value: 'star5',
+      icon: 'people-outline',
+    },
+  ];
+  currentValue: string = 'recommend';
+  oldCurrentValue:string = 'recommend';
+  isOpen: boolean = false; //显示选择弹窗
   banner: Array<Parse.Object> = [];
-  roomList:Array<Parse.Object> = []
+  roomList: Array<Parse.Object> = [];
   pageSwiper: Swiper | undefined | any;
 
   constructor(private router: Router, public authServ: AuthService) {}
@@ -25,13 +70,13 @@ export class HomeComponent implements OnInit {
     this.refresh();
   }
   async refresh() {
-    await this.getBanner()
-    await this.getRoom()
+    await this.getBanner();
+    await this.getRoom();
     setTimeout(() => {
-      this.initSwiperTimeEvent()
+      this.initSwiperTimeEvent();
     }, 0);
   }
-  async getBanner(){
+  async getBanner() {
     let query = new Parse.Query('Banner');
     query.equalTo('company', this.authServ.company);
     query.descending('index');
@@ -39,41 +84,55 @@ export class HomeComponent implements OnInit {
     query.notEqualTo('isDeleted', true);
     let banner = await query.find();
     this.banner = banner;
-
   }
   initSwiperTimeEvent() {
     // 初始化轮播图
-   let swiper = new Swiper(".mySwiper", {
+    let swiper = new Swiper('.mySwiper', {
       loop: true, // 循环模式选项
-      observer: false,//修改swiper自己或子元素时,自动初始化swiper
-      observeParents: true,//修改swiper的父元素时,自动初始化swiper
-      autoplay:{ 
-        delay:1500,
-      }, 
+      observer: false, //修改swiper自己或子元素时,自动初始化swiper
+      observeParents: true, //修改swiper的父元素时,自动初始化swiper
+      autoplay: {
+        delay: 1500,
+      },
       pagination: {
-        el: ".swiper-pagination",
+        el: '.swiper-pagination',
       },
     });
-    swiper.on('slideChange', function (event:any) {
+    swiper.on('slideChange', function (event: any) {
       // console.log(event);
     });
   }
-  async getRoom(){
+  async getRoom() {
     let query = new Parse.Query('Room');
     query.equalTo('company', this.authServ.company);
-    query.equalTo('state',true);
+    query.equalTo('state', true);
     query.notEqualTo('isDeleted', true);
-    let r = await query.find()
-    this.roomList = r
+    let r = await query.find();
+    this.roomList = r;
   }
   segmentChanged(e: any) {
     let { value } = e.detail;
-    this.active = value;
+    this.currentValue = value;
+  }
+  onIonChange(event: CustomEvent) {
+    this.currentValue = event.detail.value;
+  }
+  /* 关闭弹窗回调 */
+  onDidDismiss(event: CustomEvent) {
+    this.isOpen = false;
+    console.log(this.currentValue);
   }
-  toSearchb() {
-    this.router.navigate(['metapunk/searchbar']);
+  cancel(type: string, value?: string) {
+    console.log(type, value);
+    if(type == 'cancel'){
+      this.currentValue = this.oldCurrentValue;
+    }else{
+      this.oldCurrentValue = this.currentValue;
+    }
+    this.isOpen = false;
+    this.modal.dismiss();
   }
-  search(){
+  search() {
     this.router.navigate(['live/search']);
   }
 }

+ 4 - 4
projects/live-app/src/moduls/tabs/my/my.component.html

@@ -19,15 +19,15 @@
         </div>
         <div class="user-block">
           <div class="tags">
-            <!-- @if (user?.get('sex') == '男') { -->
+            @if (user?.get('sex') == '男') {
             <div class="sex">
               <ion-icon name="male-outline"></ion-icon>
             </div>
-            <!-- }@else if (user?.get('sex') == '女') {
+            }@else if (user?.get('sex') == '女') {
             <div class="sex girl">
               <ion-icon name="male-female-outline"></ion-icon>
             </div>
-            } -->
+            }
             <div class="age">
               <img
                 src="https://file-cloud.fmode.cn/Qje9D4bqol/20241109/pctmvt110807052.png"
@@ -137,7 +137,7 @@
           <ion-icon name="remove-circle-outline"></ion-icon>
           勿扰开关
         </div>
-        <ion-toggle [checked]="true" color="tertiary"></ion-toggle>
+        <ion-toggle [checked]="profile?.get('isCheck')" (ionChange)="onChange($event)" color="tertiary"></ion-toggle>
       </div>
     </div>
     <div class="list">

+ 52 - 8
projects/live-app/src/moduls/tabs/my/my.component.ts

@@ -1,5 +1,9 @@
 import { Component, OnInit } from '@angular/core';
-import { IonicModule } from '@ionic/angular';
+import {
+  IonicModule,
+  LoadingController,
+  ToastController,
+} from '@ionic/angular';
 
 import * as Parse from 'parse';
 import { AgreementComponent } from '../../login/agreement/agreement.component';
@@ -14,11 +18,14 @@ import { AuthService } from '../../../services/auth.service';
   imports: [IonicModule],
 })
 export class MyComponent implements OnInit {
+  profile?: Parse.Object; //身份信息
   user?: Parse.Object = Parse.User.current();
   constructor(
+    public loadingCtrl: LoadingController,
     private modalController: ModalController,
     private alertController: AlertController,
     public authServ: AuthService,
+    public toastController: ToastController,
     private router: Router
   ) {}
   tools: Array<{ icon: string; title: string; path: string }> = [
@@ -35,20 +42,30 @@ export class MyComponent implements OnInit {
     {
       icon: '/img/邀请.png',
       title: '邀请',
-      path: '',
+      path: 'user/share',
     },
     {
       icon: '/img/设置.png',
       title: '设置',
-      path: '',
+      path: 'user/setting',
     },
   ];
   registerAgreement: any; //用户协议
   liveAgreement: any; //直播协议
   company: string | null = localStorage?.getItem('company');
   ngOnInit() {
+    this.getProfile();
     this.getAgreement();
   }
+  // 获取用户信息
+  async getProfile() {
+    let user = Parse.User.current();
+    let query = new Parse.Query('Profile');
+    query.equalTo('user', user?.id);
+    query.notEqualTo('isDeleted', true);
+    query.select('isCheck');
+    this.profile = await query.first();
+  }
   getAgreement() {
     let Agreement = new Parse.Query('ContractAgreement');
     Agreement.equalTo('company', this.company);
@@ -65,6 +82,34 @@ export class MyComponent implements OnInit {
       this.liveAgreement = res;
     });
   }
+  async onChange(e: any) {
+    const loading = await this.loadingCtrl.create({
+      message: '正在修改',
+    });
+    loading.present();
+    let checked = e.detail.checked;
+    console.log(checked);
+    if (!this.profile?.id) {
+      let obj = Parse.Object.extend('Profile');
+      this.profile = new obj();
+      this.profile?.set('mobile', Parse.User.current()?.get('mobile'));
+      this.profile?.set('user', Parse.User.current()?.toPointer());
+      this.profile?.set('company', {
+        __type: 'Pointer',
+        className: 'Company',
+        objectId: this.authServ.company,
+      });
+    }
+    this.profile?.set('isCheck', checked);
+    await this.profile?.save();
+    loading.dismiss();
+    const toast = await this.toastController.create({
+      message: `已${checked ? '开启' : '关闭'}`,
+      color: 'success',
+      duration: 1000,
+    });
+    toast.present();
+  }
   toUrl(url: string, params?: Object) {
     console.log(url);
     if (params) {
@@ -85,8 +130,8 @@ export class MyComponent implements OnInit {
       return await modal.present();
     }
   }
-  async onLogout(){
-      const alert = await this.alertController.create({
+  async onLogout() {
+    const alert = await this.alertController.create({
       cssClass: 'my-custom-class',
       header: '',
       message: '你确定退出登录吗?',
@@ -97,13 +142,12 @@ export class MyComponent implements OnInit {
           cssClass: 'secondary',
           handler: (blah) => {
             console.log('Confirm Cancel: blah');
-            this.authServ.logout()
+            this.authServ.logout();
           },
         },
         {
           text: '取消',
-          handler: () => {
-          },
+          handler: () => {},
         },
       ],
     });

+ 10 - 9
projects/live-app/src/moduls/user/album/album.component.ts

@@ -14,12 +14,12 @@ import { AuthService } from '../../../services/auth.service';
   templateUrl: './album.component.html',
   styleUrls: ['./album.component.scss'],
   standalone: true,
-  imports: [IonicModule, UploadComponent,NavComponent],
+  imports: [IonicModule, UploadComponent, NavComponent],
 })
 export class AlbumComponent implements OnInit {
   files: any = [];
   profile?: Parse.Object;
-  loading:boolean = false
+  loading: boolean = false;
   constructor(
     public loadCtrl: LoadingController,
     public toastController: ToastController,
@@ -39,7 +39,7 @@ export class AlbumComponent implements OnInit {
     this.files = p?.get('attachment')?.map((item: String) => {
       return { url: item };
     });
-    this.loading = true
+    this.loading = true;
   }
   async onSave(e: any) {
     console.log(e);
@@ -47,13 +47,14 @@ export class AlbumComponent implements OnInit {
     if (!this.profile?.id) {
       let obj = Parse.Object.extend('Profile');
       this.profile = new obj();
+      this.profile?.set('mobile', Parse.User.current()?.get('mobile'));
+      this.profile?.set('user', Parse.User.current()?.toPointer());
+      this.profile?.set('company', {
+        __type: 'Pointer',
+        className: 'Company',
+        objectId: this.authServ.company,
+      });
     }
-    this.profile?.set('user', Parse.User.current()?.toPointer());
-    this.profile?.set('company', {
-      __type: 'Pointer',
-      className: 'Company',
-      objectId: this.authServ.company,
-    });
     this.profile?.set('attachment', urls);
     await this.profile?.save();
     const toast = await this.toastController.create({

+ 58 - 12
projects/live-app/src/moduls/user/profile/profile.component.html

@@ -49,34 +49,80 @@
     <div class="bar"></div>
   </div>
   <div class="user-data">
-    <div class="title-h2">资料</div>
-    <div class="user-data">
+    <div class="tabs">
+      <div
+        [ngClass]="{ active: active == 0, 'title-h2': true }"
+        (click)="active = 0"
+      >
+        资料
+      </div>
+      <div
+        [ngClass]="{ active: active == 1, 'title-h2': true }"
+        (click)="active = 1"
+      >
+        相册
+      </div>
+    </div>
+    @if (active == 0) {
+    <div class="data-row">
       <div class="title-text">个人资料</div>
       <div class="tags">
-        <span>生日:未知</span>
-        <span>星座:未知</span>
-        <span>城市:未知</span>
+        <span class="label">生日:未知</span>
+        <span class="label">星座:未知</span>
+        <span class="label">城市:未知</span>
       </div>
       <div class="motto">忽悠古人心上过,回首山河已是秋。</div>
     </div>
-    <div class="user-data">
+    <div class="data-row">
+      <div class="title-text">ta的社交标签</div>
+      <div class="tags">
+        <span class="label tag">可爱</span>
+        <span class="label tag">天真</span>
+        <span class="label tag">漂亮</span>
+        <span class="label tag">萝莉控</span>
+        <span class="label tag">声优</span>
+      </div>
+    </div>
+    <div class="data-row">
+      <div class="title-text">对ta的评价</div>
+      <div class="tags">
+        <span class="label assess">漂亮小姐姐</span>
+        <span class="label assess">颜值主播</span>
+        <span class="label assess">唱歌好听</span>
+        <span class="label assess">萝莉控</span>
+        <span class="label assess">声优</span>
+        <span class="label assess">喜欢日漫</span>
+        <span class="label assess">都喜欢</span>
+      </div>
+    </div>
+    <div class="data-row">
+      <div class="title-text">ta的礼物墙</div>
+      <div class="gift">
+        @for (item of profile?.get('gifts'); track $index) {
+        <img [src]="item" alt="" />
+        }
+      </div>
+    </div>
+    }@else {
+    <div class="data-row">
       <div class="title-text">个人相册</div>
       <div class="album">
         @for (item of profile?.get('album'); track $index) {
-        <img [src]="item" alt="" />
+        <img [src]="item" alt="" (click)="onShowImg(item)" />
         }
       </div>
     </div>
-    <div class="user-data">
-      <div class="title-text">礼物墙</div>
-    </div>
+    }
   </div>
 </ion-content>
 <ion-footer class="footer">
   <ion-toolbar class="footer-tool">
     <div class="btns">
-      <div class="round">私信</div>
-      <div class="round">送礼</div>
+      <div class="round">
+        <ion-icon name="chatbubble-outline"></ion-icon>私信
+      </div>
+      <div class="round"><ion-icon name="gift-outline"></ion-icon>送礼</div>
     </div>
   </ion-toolbar>
 </ion-footer>
+<app-image-preview [image]="currenImg" #preview></app-image-preview>

+ 103 - 16
projects/live-app/src/moduls/user/profile/profile.component.scss

@@ -42,7 +42,7 @@
           .top-right-block {
             display: flex;
             align-items: end;
-            .top-left-title{
+            .top-left-title {
               font-size: 20px;
               font-weight: 500;
             }
@@ -82,13 +82,13 @@
           display: flex;
           align-items: center;
           font-size: 12px;
-          .id{
+          .id {
             color: #1e1e1e;
           }
-          .tag{
+          .tag {
             color: white;
             background: #fe454e;
-            padding: 2px 6px;
+            padding: 2px 10px;
             border-radius: 10px;
             margin-left: 10px;
           }
@@ -96,39 +96,126 @@
       }
       .user-right {
         flex-shrink: 0;
-        ion-icon{
+        ion-icon {
           font-size: 40px;
           color: #fe454e;
         }
       }
     }
-    .user-fans{
+    .user-fans {
       font-size: 14px;
-      span{
+      span {
         margin-right: 10px;
       }
     }
-    .bar{
+    .bar {
       margin-top: 10px;
       border-bottom: 1px solid #dbdbdb;
       width: 100%;
     }
   }
-  .user-data{
+  .user-data {
     width: 100%;
     padding: 6px 10px;
+    .tabs{
+      display: flex;
+      justify-content: space-evenly;
+      margin: 10px auto;
+    }
+    .title-h2 {
+      font-size: 16px;
+      font-weight: bold;
+      // border-bottom: 1px solid #dbdbdb;
+      display: table-row-group;
+    }
+    .active::after {
+      content: "";
+      width: 60%;
+      height: 1px;
+      display: block;
+      margin: 0 auto;
+      border-bottom: 2px solid rgb(0 0 0 / 98%);
+      border-radius: 10px;
+    }
+    .data-row{
+      margin-top: 10px;
+      .title-text{
+        font-size: 14px;
+        font-weight: bold;
+      }
+      .tags{
+        display: flex;
+        flex-wrap: wrap;
+        .label{
+          display: inline-block;
+          background: #ff7378;
+          padding: 2px 10px;
+          border-radius: 20px;
+          font-size: 12px;
+          margin: 6px 6px 6px 0;
+          color: white;
+        }
+        .tag{
+          background: #FFFFFF;
+          border-radius: 4px;
+          border: 1px solid ;
+          color: #7045ff;
+        }
+        .assess{
+          background: #FFFFFF;
+          border-radius: 4px;
+          border: 1px solid ;
+          color: #7045ff;
+        }
+      }
+      .motto{
+        font-size: 12px;
+      }
+      .gift{
+        display: grid;
+        grid-template-columns: repeat(5, 70px);
+        justify-content: space-between;
+        flex-wrap: wrap;
+        img{
+          // width: 40px;
+          height: 70px;
+          border-radius: 50%;
+          object-fit: cover;
+          margin-top: 10px;
+        }
+      }
+      .album{
+        display: grid;
+        grid-template-columns: repeat(5, 70px);
+        justify-content: space-between;
+        flex-wrap: wrap;
+        img{
+          // width: 40px;
+          height: 70px;
+          object-fit: cover;
+          margin-top: 10px;
+        }
+      }
+    }
   }
 }
-.footer{
-  .btns{
+.footer {
+  .btns {
     display: flex;
     justify-content: space-around;
-    .round{
-      background: #156bfd;
-      padding: 8px 40px;
+    .round {
+      // background: #156bfd;
+      padding: 8px 30px;
       border-radius: 20px;
-      color: white;
+      color: #156bfd;
       font-size: 14px;
+      display: flex;
+      align-items: center;
+      border: 1px solid #156bfd;
+      ion-icon{
+        font-size: 20px;
+        margin-right: 10px;
+      }
     }
   }
-}
+}

+ 14 - 4
projects/live-app/src/moduls/user/profile/profile.component.ts

@@ -1,5 +1,5 @@
-import { Component, OnInit } from '@angular/core';
-import { DatePipe } from '@angular/common';
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { CommonModule, DatePipe } from '@angular/common';
 import { NavComponent } from '../../../app/components/nav/nav.component';
 import { Router } from '@angular/router';
 import {
@@ -7,22 +7,27 @@ import {
   ToastController,
 } from '@ionic/angular';
 import * as Parse from 'parse';
+import { ImagePreviewComponent } from '../../../app/components/image-preview/image-preview.component';
 
 @Component({
   selector: 'app-profile',
   templateUrl: './profile.component.html',
   styleUrls: ['./profile.component.scss'],
   standalone: true,
-  imports: [IonicModule, DatePipe,NavComponent],
+  imports: [IonicModule, DatePipe,NavComponent,CommonModule,ImagePreviewComponent],
   providers: [DatePipe],
 })
 export class ProfileComponent implements OnInit {
+  @ViewChild('preview') preview!:ImagePreviewComponent
+  
   profile: Parse.Object | any = {
     get: (e: string) => {
       let jsonb:any = {
         'cover': `https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg`,
         'user':Parse.User.current(),
         'name':'甜心宝贝',
+        'album':['https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg','https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg','https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg','https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg',],
+        'gifts':['https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg','https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg','https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg','https://file-cloud.fmode.cn/Qje9D4bqol/20241119/sd1upp041122290.jpg',],
         '': '',
       };
       return jsonb[e];
@@ -31,6 +36,8 @@ export class ProfileComponent implements OnInit {
     save: () => {},
     destroy: () => {}, // 修复:添加方法体
   };
+  active:number = 0;
+  currenImg:string = '';
   constructor(
     private router: Router,
     public toastController: ToastController,
@@ -38,5 +45,8 @@ export class ProfileComponent implements OnInit {
 
   ngOnInit() {
   }
-
+  onShowImg(url:string){
+    this.currenImg = url
+    this.preview.show = 'inline-flex'
+  }
 }

+ 87 - 0
projects/live-app/src/moduls/user/setting/setting.component.html

@@ -0,0 +1,87 @@
+<nav title="编辑信息"></nav>
+<ion-content class="content">
+  <ion-list>
+    <ion-item>
+      <div class="row portrait">
+        <div>头像</div>
+        @if (formData.avatar) {
+        <!-- <img [src]="avatar" alt="" /> -->
+        <app-upload
+          (onChange)="saveEdit($event)"
+          #upload
+          [maxlenght]="1"
+          [files]="[{ url: formData.avatar }]"
+          [fileWidth]="80"
+          [fileHeight]="80"
+          [boxWidth]="100"
+        ></app-upload>
+        }
+      </div>
+    </ion-item>
+    <ion-item>
+      <div class="row">
+        <div>昵称</div>
+        <div class="right">
+          <ion-input
+            placeholder="请输入昵称"
+            value="{{ formData.nickname }}"
+            (ionChange)="onNickname($event)"
+          ></ion-input>
+          <ion-icon name="chevron-forward-outline"></ion-icon>
+        </div>
+      </div>
+    </ion-item>
+    <ion-item>
+      <div class="row">
+        <div>真实姓名</div>
+        <div class="right">
+          <ion-input
+            placeholder="待认证"
+            value="{{ formData.name }}"
+            [disabled]="true"
+          ></ion-input>
+          <ion-icon name="chevron-forward-outline"></ion-icon>
+        </div>
+      </div>
+    </ion-item>
+    <ion-item>
+      <ion-select
+        label="性别"
+        placeholder=""
+        (ionChange)="onSelect($event)"
+        interface="popover"
+        placeholder="{{ formData.sex }}"
+      >
+        <ion-select-option value="男">男</ion-select-option>
+        <ion-select-option value="女">女</ion-select-option>
+      </ion-select>
+    </ion-item>
+    <ion-item>
+      <div class="row">
+        <div>年龄</div>
+        <div class="right">
+          <ion-input
+            placeholder="请输入年龄"
+            value="{{ formData.age }}"
+            (ionChange)="onInput($event)"
+          ></ion-input>
+          <ion-icon name="chevron-forward-outline"></ion-icon>
+        </div>
+      </div>
+    </ion-item>
+    <ion-item>
+      <div class="row">
+        <div>手机号</div>
+        <div class="right">
+          <span>{{ mobile }}</span
+          ><ion-icon name="chevron-forward-outline"></ion-icon>
+        </div>
+      </div>
+    </ion-item>
+    <!-- <div class="address" (click)="toAddress()">
+      <span>收货地址管理</span
+      ><ion-icon name="chevron-forward-outline"></ion-icon>
+    </div> -->
+  </ion-list>
+  <button class="submit" (click)="upload.onUpload()">保存修改</button>
+</ion-content>

+ 67 - 0
projects/live-app/src/moduls/user/setting/setting.component.scss

@@ -0,0 +1,67 @@
+.header {
+  background: #ffffff;
+}
+
+ion-content {
+  --background: none;
+  background: #f5f6fa;
+  --padding-bottom: 26.6667vw;
+  .portrait {
+    // background-color: white;
+    // padding: 1.3333vw 4vw 0 4vw;
+    // font-size: 4.2667vw;
+    // display: flex;
+    // justify-content: space-between;
+
+    div {
+      line-height: 16vw;
+    }
+    img {
+      border-radius: 50%;
+      width: 16vw;
+      height: 16vw;
+    }
+  }
+
+  .row {
+    // height: 13.3333vw;
+    // font-size: 4.2667vw;
+    background-color: white;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    // padding:0 4vw;
+    // border: 0.2667vw solid #f6f6f6;
+    .right {
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+    }
+    span {
+      margin-right: 4vw;
+    }
+    ion-input {
+      text-align: right;
+    }
+  }
+  // .age,.nikename{
+  //     >div:last-child{
+  //         display: flex;
+  //         align-items: center;
+  //         text-align: right;
+  //     }
+  // }
+
+  .submit {
+    position: fixed;
+    bottom: 8vw;
+    width: 80%;
+    height: 10.6667vw;
+    font-size: 4.2667vw;
+    color: white;
+    margin: 0 10%;
+    background-color: #fe4d54;
+    border-radius: 10px;
+  }
+}

+ 28 - 0
projects/live-app/src/moduls/user/setting/setting.component.spec.ts

@@ -0,0 +1,28 @@
+/* tslint:disable:no-unused-variable */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+import { SettingComponent } from './setting.component';
+
+describe('SettingComponent', () => {
+  let component: SettingComponent;
+  let fixture: ComponentFixture<SettingComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ SettingComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SettingComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 156 - 0
projects/live-app/src/moduls/user/setting/setting.component.ts

@@ -0,0 +1,156 @@
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import {
+  AlertController,
+  IonicModule,
+  LoadingController,
+  ToastController,
+} from '@ionic/angular';
+import { NavComponent } from '../../../app/components/nav/nav.component';
+import * as Parse from 'parse';
+import { Router } from '@angular/router';
+import { UploadComponent } from '../../../app/components/upload/upload.component';
+import { AuthService } from '../../../services/auth.service';
+
+@Component({
+  selector: 'app-setting',
+  templateUrl: './setting.component.html',
+  styleUrls: ['./setting.component.scss'],
+  standalone: true,
+  imports: [IonicModule, NavComponent, UploadComponent],
+})
+export class SettingComponent implements OnInit {
+  @ViewChild('upload') upload!: UploadComponent;
+  profile?: Parse.Object; //身份信息
+  formData: any = {
+    avatar: '',
+    name: '',
+    nickname: '',
+    sex: '',
+    age: '',
+  };
+  mobile: string = '';
+  loading: any;
+  constructor(
+    private alertController: AlertController,
+    public loadingCtrl: LoadingController,
+    public toastController: ToastController,
+    private authServ: AuthService,
+    private router: Router
+  ) {}
+
+  ngOnInit() {
+    this.getProfile();
+  }
+  // 获取用户信息
+  async getProfile() {
+    this.loading = await this.loadingCtrl.create({
+      message: '加载中',
+    });
+    this.loading.present();
+    let user = Parse.User.current();
+    let query = new Parse.Query('Profile');
+    query.equalTo('user', user?.id);
+    query.notEqualTo('isDeleted', true);
+    this.profile = await query.first();
+    this.formData.avatar =
+      user?.get('avatar') ||
+      'https://file-cloud.fmode.cn/DXNgcD6zo6/20221202/j6p8kb034039.png';
+    this.formData.nickname = user?.get('nickname') || '';
+    this.formData.sex = user?.get('sex') || '';
+    this.formData.age = user?.get('age') || '';
+    this.mobile = user?.get('mobile');
+    this.formData.name = this.profile?.get('name') || '';
+    this.formData.age = this.profile?.get('birthdate') || '';
+    this.loading.dismiss();
+  }
+
+  //筛选
+  onSelect(e: any) {
+    this.formData.sex = e.detail.value;
+  }
+  //年龄
+  onInput(e: any) {
+    this.formData.age = e.detail.value;
+  }
+  //昵称
+  onNickname(e: any) {
+    this.formData.nickname = e.detail.value;
+  }
+  async saveEdit(e: any) {
+    this.loading = await this.loadingCtrl.create({
+      message: '正在保存修改',
+    });
+    this.loading.present();
+    console.log(e);
+    console.log(this.formData);
+    let urls = e?.map((item: any) => item.url);
+    let user = Parse.User.current();
+    if (!user?.id) {
+      this.loading.dismiss();
+      const alert = await this.alertController.create({
+        header: '提示',
+        message: '登录信息已过期,请重新登录',
+        buttons: [
+          {
+            text: '好的',
+            role: 'confirm',
+            handler: () => {
+              this.authServ.logout();
+            },
+          },
+        ],
+      });
+      await alert.present();
+      return;
+    }
+    user.set('avatar', urls[0]);
+    user.set('nickname', this.formData.nickname);
+    user.set('sex', this.formData.sex);
+    // user.set('age', Number(this.formData.age));
+    await user.save();
+    if (!this.profile?.id) {
+      let obj = Parse.Object.extend('Profile');
+      this.profile = new obj();
+      this.profile?.set('mobile', Parse.User.current()?.get('mobile'));
+      this.profile?.set('sex', this.formData.sex);
+      this.profile?.set('birthdate', this.formData.age);
+    }
+    this.profile?.set('user', Parse.User.current()?.toPointer());
+    this.profile?.set('company', {
+      __type: 'Pointer',
+      className: 'Company',
+      objectId: this.authServ.company,
+    });
+    await this.profile?.save();
+    this.loading.dismiss();
+    this.getProfile();
+    this.presentAlert('修改成功');
+  }
+
+  async presentToast(title: string, time: number, color: string) {
+    const toast = await this.toastController.create({
+      message: title,
+      duration: time,
+      color: color,
+    });
+    toast.present();
+  }
+
+  async presentAlert(msg: string) {
+    const alert = await this.alertController.create({
+      header: '提示',
+      message: msg,
+      buttons: [
+        {
+          text: '好的',
+          role: 'confirm',
+          handler: () => {},
+        },
+      ],
+    });
+    await alert.present();
+  }
+  // toAddress() {
+  //   this.router.navigate(['/metapunk/address'])
+  // }
+}

+ 24 - 0
projects/live-app/src/moduls/user/share/share.component.html

@@ -0,0 +1,24 @@
+<nav title="邀请好友"></nav>
+<div class="content">
+  <div class="share">
+    <div class="share-view">
+      <img [src]="imgUrl" height="480" width="300" class="poster" id="banner" />
+    </div>
+    <div class="share-footre">
+      <div class="share-btn" (click)="copyText()">
+        <img
+          class="share-icon"
+          src="https://file-cloud.fmode.cn/UP2cStyjuk/20231113/n3v3v7031154456.png"
+        />
+        复制链接
+      </div>
+      <div class="share-btn" (click)="downImg()">
+        <img
+          class="share-icon"
+          src="https://file-cloud.fmode.cn/UP2cStyjuk/20231113/i469vf031154765.png"
+        />
+        图片分享
+      </div>
+    </div>
+  </div>
+</div>

+ 45 - 0
projects/live-app/src/moduls/user/share/share.component.scss

@@ -0,0 +1,45 @@
+.content {
+	height: 100vh;
+	width: 100%;
+	background-image: url("https://file-cloud.fmode.cn/uiZD6NisQm/20220831/g1bkbm102855.png");
+	background-repeat: no-repeat;
+	background-position: center top;
+	background-size: 100% 100%;
+  padding: 10px;
+  .share {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    .share-view {
+      min-height: 300px;
+      .canvas,
+      .poster {
+        width: 300px;
+        height: 480px;
+      }
+    }
+    .share-footre {
+      margin-top: 20px;
+      display: flex;
+      justify-content: space-evenly;
+      width: 100%;
+      user-select: none;
+      .share-btn {
+        cursor: pointer;
+        font-size: 16px;
+        display: flex;
+        align-items: center;
+        img {
+          margin-right: 4px;
+        }
+      }
+      .share-btn:active {
+        color: #1890ff;
+      }
+      .share-icon {
+        width: 2rem;
+        height: 2rem;
+      }
+    }
+  }
+}

+ 28 - 0
projects/live-app/src/moduls/user/share/share.component.spec.ts

@@ -0,0 +1,28 @@
+/* tslint:disable:no-unused-variable */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+import { ShareComponent } from './share.component';
+
+describe('ShareComponent', () => {
+  let component: ShareComponent;
+  let fixture: ComponentFixture<ShareComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ShareComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ShareComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 116 - 0
projects/live-app/src/moduls/user/share/share.component.ts

@@ -0,0 +1,116 @@
+import { HttpClient } from '@angular/common/http';
+import { Component, Input, OnInit } from '@angular/core';
+import { IonicModule, ToastController } from '@ionic/angular';
+import { NavComponent } from '../../../app/components/nav/nav.component';
+import * as Parse from 'parse';
+
+@Component({
+  selector: 'app-share',
+  templateUrl: './share.component.html',
+  styleUrls: ['./share.component.scss'],
+  standalone: true,
+  imports: [IonicModule, NavComponent],
+})
+export class ShareComponent implements OnInit {
+  @Input() path: string = '/chat/home';
+
+  imgUrl: string = '';
+  //海报
+  bannerUrl: string =
+    'https://file-cloud.fmode.cn/E4KpGvTEto/20231114/vot1d4114914635.jpg';
+  codeUrl: string = '';
+  constructor(
+    public toastController: ToastController,
+    private http: HttpClient
+  ) {}
+
+  ngOnInit() {
+    this.getCode()
+  }
+  ngOnChanges() {
+    this.getCode();
+  }
+  getCode() {
+    console.log('getcode');
+    let uid = Parse.User.current()?.id;
+    let qrCodeUrl = `https://aichat.fmode.cn${this.path}?invite=${uid}`;
+    let params = {
+      qrCode: qrCodeUrl,
+      darkColor: '#ffffff',
+      lightColor: '#000000',
+    };
+    this.http
+      .get('https://server.fmode.cn/api/common/qrcode', { params })
+      .subscribe((res: any) => {
+        // console.log(res);
+        this.codeUrl = res.data;
+        this.drawPoster();
+      });
+  }
+
+  //绘画海报
+  async drawPoster() {
+    let canvas: any = document.createElement('canvas');
+    canvas.height = '480';
+    canvas.width = '300';
+    let ctx = canvas.getContext('2d');
+    //定义图片
+    ctx.drawImage(await this.compileImage(this.bannerUrl), 0, 0, 340, 540);
+    //绘制二维码
+    ctx.drawImage(await this.compileImage(this.codeUrl), 95, 318, 148, 148);
+    let tempSrc = canvas.toDataURL('image/png');
+    this.imgUrl = tempSrc;
+    document.body.removeChild(canvas);
+  }
+  compileImage(url: string): Promise<any> {
+    return new Promise((res) => {
+      let img = new Image();
+      img.src = url;
+      img.setAttribute('crossOrigin', 'anonymous');
+      img.onload = function () {
+        res(img);
+      };
+    });
+  }
+
+  /**
+   * 返回当前元素的文本内容
+   */
+  async copyText() {
+    let uid = Parse.User.current()?.id;
+    let text = `https://ai.fmode.cn${this.path}?invite=${uid}`;
+    // let oInput = document.createElement("input");
+    // oInput.value = text;
+    // document.body.appendChild(oInput);
+    // oInput.select(); // 选择对象
+    // oInput.setAttribute("readonly", "readonly"); //设置只读属性防止手机上弹出软键盘
+    // document.execCommand("Copy"); // 执行浏览器复制命令
+    // oInput.style.display = "none";
+    // document.body.removeChild(oInput); //移除DOM元素
+
+    navigator.clipboard.writeText(text);
+    console.log(text);
+    console.log('复制成功');
+    const toast = await this.toastController.create({
+      message: '复制成功',
+      color: 'success',
+      duration: 1000,
+    });
+    toast.present();
+    window.location.href = 'weixin://';
+  }
+  /* 下载海报 */
+  downImg() {
+    let dlLink: any = document.createElement('a');
+    if ('download' in dlLink) {
+      dlLink.style.visibility = 'hidden';
+      dlLink.href = this.imgUrl;
+      dlLink.download = '分享海报';
+      document.body.appendChild(dlLink);
+      dlLink.click();
+      document.body.removeChild(dlLink);
+    } else {
+      location.href = this.imgUrl;
+    }
+  }
+}

+ 11 - 0
projects/live-app/src/moduls/user/user.modules.routes.ts

@@ -4,6 +4,8 @@ import { AlbumComponent } from './album/album.component';
 import { CertificationComponent } from './certification/certification.component';
 import { FeedbackComponent } from './feedback/feedback.component';
 import { ProfileComponent } from './profile/profile.component';
+import { SettingComponent } from './setting/setting.component';
+import { ShareComponent } from './share/share.component';
 const routes: Routes = [
   {
     path: 'profile',//实名
@@ -21,6 +23,15 @@ const routes: Routes = [
     path: 'feedback',//意见反馈
     component: FeedbackComponent,
   },
+  {
+    path: 'share',//分享
+    component: ShareComponent,
+  },
+  {
+    path: 'setting',//设置
+    component: SettingComponent,
+  },
+  
 ]
 @NgModule({
   imports: [RouterModule.forChild(routes)],

+ 13 - 0
projects/live-app/src/services/aichart.service.ts

@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import * as Parse from 'parse';
+import { Router } from '@angular/router';
+@Injectable({
+  providedIn: 'root',
+})
+export class AiChatService {
+  isLoggedIn = false;
+  company:string = 'Qje9D4bqol'
+  constructor(private router: Router) {
+  }
+
+}

+ 0 - 3
projects/live-app/src/services/auth.service.ts

@@ -1,13 +1,10 @@
 import { Injectable } from '@angular/core';
 import * as Parse from 'parse';
-import { Observable, of } from 'rxjs';
-import { tap, delay } from 'rxjs/operators';
 import { Router } from '@angular/router';
 @Injectable({
   providedIn: 'root',
 })
 export class AuthService {
-  isLoggedIn = false;
   company:string = 'Qje9D4bqol'
   redirectUrl: string = 'tabs';
   constructor(private router: Router) {}