Procházet zdrojové kódy

Merge branch 'master' of http://git.fmode.cn:3000/0225273/APPmy

0225263 před 4 měsíci
rodič
revize
1ef73c35ea

+ 14 - 0
src/app/aichat/aichat.page.html

@@ -19,6 +19,20 @@
     </ion-item>
   </ion-list>
 
+  <ion-list lines="none">
+    <ion-item *ngFor="let message of messageList">
+      <ion-avatar slot="start">
+        <ion-img *ngIf="message?.role=='assistant'" src="assets/img/d.webp"></ion-img>
+        <ion-img *ngIf="message?.role=='user'" src="assets/img/a.png"></ion-img>
+      </ion-avatar>
+      <ion-label>
+        <h3>{{ message.role }}</h3>
+        <p>{{ message.content }}</p>
+        <!-- <p class="message-time">{{ message.timestamp | date: 'medium' }}</p> -->
+      </ion-label>
+    </ion-item>
+  </ion-list>
+
   <ion-item>
     <ion-input placeholder="请输入消息" [(ngModel)]="newMessage"></ion-input>
     <ion-button slot="end" (click)="sendMessage()">发送</ion-button>

+ 29 - 1
src/app/aichat/aichat.page.ts

@@ -1,4 +1,5 @@
 import { Component, OnInit } from '@angular/core';
+import { TestRxjsChatCompletion, TestRxjsChatMessage } from './chat-completion';
 
 @Component({
   selector: 'app-aichat',
@@ -13,7 +14,7 @@ export class AIChatPage implements OnInit {
   ];
   newMessage: string = '';
 
-  sendMessage() {
+  sendMessage123() {
     if (this.newMessage.trim() !== '') {
       this.chatMessages.push({ sender: 'User', senderAvatar: 'assets/user-avatar.png', content: this.newMessage, timestamp: new Date() });
       this.newMessage = ''; // 清空输入框
@@ -23,4 +24,31 @@ export class AIChatPage implements OnInit {
   ngOnInit() {
   }
 
+  messageList:Array<TestRxjsChatMessage> = []
+  sendMessage(){
+    this.messageList.push({
+      role:"user",
+      content: this.newMessage
+    })
+    this.newMessage = ""
+    
+    // this.completion.createCompletionByStream()
+
+    // messageList在competion内部,已经赋予了完整的message
+    // 下方暴露出来的可订阅内容,主要是用于关键字过滤,或者其他开发逻辑的续写
+    let testChatCompletion = new TestRxjsChatCompletion(this.messageList);
+    testChatCompletion.createCompletionByStream().subscribe({
+        next: ({ content, cumulativeContent, done }) => {
+            console.log(`Content: ${content}`);
+            console.log(`Cumulative Content: ${cumulativeContent}`);
+            if (done) {
+                console.log('Stream completed');
+            }
+        },
+        error: err => console.error(err),
+        complete: () => console.log('Observable completed')
+    });
+
+  }
+
 }

+ 111 - 0
src/app/aichat/chat-completion.ts

@@ -0,0 +1,111 @@
+import { Observable, from, of } from 'rxjs';
+import { switchMap, map, catchError, finalize } from 'rxjs/operators';
+
+export interface TestRxjsChatMessage {
+    role: string;
+    content: string;
+}
+
+export class TestRxjsChatCompletion {
+    messageList: Array<TestRxjsChatMessage>;
+
+    constructor(messageList: Array<TestRxjsChatMessage>) {
+        this.messageList = messageList;
+    }
+
+    createCompletionByStream(options?:{
+        model?:string
+    }): Observable<{ content: string, cumulativeContent: string, done: boolean }> {
+        const token = localStorage.getItem("token");
+        const bodyJson = {
+            "token": `Bearer ${token}`,
+            "messages": this.messageList,
+            "model": options?.model || "fmode-3.6-16k",
+            "temperature": 0.5,
+            "presence_penalty": 0,
+            "frequency_penalty": 0,
+            "top_p": 1,
+            "stream": true
+        };
+
+        return from(fetch("https://test.fmode.cn/api/apig/aigc/gpt/v1/chat/completions", {
+            "headers": {
+                "accept": "text/event-stream",
+                "sec-fetch-dest": "empty",
+                "sec-fetch-mode": "cors",
+                "sec-fetch-site": "same-site"
+            },
+            "referrer": "https://ai.fmode.cn/",
+            "referrerPolicy": "strict-origin-when-cross-origin",
+            "body": JSON.stringify(bodyJson),
+            "method": "POST",
+            "mode": "cors",
+            "credentials": "omit"
+        })).pipe(
+            switchMap(response => {
+                const reader = response.body?.getReader();
+                if (!reader) {
+                    throw new Error("Failed to get the response reader.");
+                }
+                const decoder = new TextDecoder();
+                let buffer = "";
+                let messageAiReply = "";
+                let messageIndex = this.messageList.length;
+
+                return new Observable<{ content: string, cumulativeContent: string, done: boolean }>(observer => {
+                    const read = () => {
+                        reader.read().then(({ done, value }) => {
+                            if (done) {
+                                observer.complete();
+                                return;
+                            }
+
+                            buffer += decoder.decode(value);
+                            let messages = buffer.split("\n");
+
+                            for (let i = 0; i < messages.length - 1; i++) {
+                                let message = messages[i];
+                                let dataText = message.replace("data: ", "");
+
+                                if (dataText.startsWith("{")) {
+                                    try {
+                                        let dataJson = JSON.parse(dataText);
+                                        let content = dataJson?.choices?.[0]?.delta?.content || "";
+                                        messageAiReply += content;
+                                        this.messageList[messageIndex] = {
+                                            role: "assistant",
+                                            content: messageAiReply
+                                        };
+                                        observer.next({ content, cumulativeContent: messageAiReply, done: false });
+                                    } catch (err) { }
+                                }
+
+                                if (dataText.startsWith("[")) {
+                                    this.messageList[messageIndex] = {
+                                        role: "assistant",
+                                        content: messageAiReply
+                                    };
+                                    observer.next({ content: "", cumulativeContent: messageAiReply, done: true });
+                                    messageAiReply = "";
+                                }
+
+                                buffer = buffer.slice(message.length + 1);
+                            }
+
+                            read();
+                        }).catch(err => observer.error(err));
+                    };
+
+                    read();
+                });
+            }),
+            catchError(err => {
+                console.error(err);
+                return of({ content: "", cumulativeContent: "", done: true });
+            }),
+            finalize(() => {
+                console.log("Stream completed");
+            })
+        );
+    }
+}

+ 4 - 0
src/app/app-routing.module.ts

@@ -64,6 +64,10 @@ const routes: Routes = [
   {
     path: 'aichat',
     loadChildren: () => import('./aichat/aichat.module').then( m => m.AIChatPageModule)
+  },
+  {
+    path: 'consult',
+    loadChildren: () => import('./consult/consult.module').then( m => m.ConsultPageModule)
   }
 
 

+ 17 - 0
src/app/consult/consult-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ConsultPage } from './consult.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ConsultPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class ConsultPageRoutingModule {}

+ 20 - 0
src/app/consult/consult.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ConsultPageRoutingModule } from './consult-routing.module';
+
+import { ConsultPage } from './consult.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    ConsultPageRoutingModule
+  ],
+  declarations: [ConsultPage]
+})
+export class ConsultPageModule {}

+ 13 - 0
src/app/consult/consult.page.html

@@ -0,0 +1,13 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>consult</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">consult</ion-title>
+    </ion-toolbar>
+  </ion-header>
+</ion-content>

+ 0 - 0
src/app/consult/consult.page.scss


+ 17 - 0
src/app/consult/consult.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ConsultPage } from './consult.page';
+
+describe('ConsultPage', () => {
+  let component: ConsultPage;
+  let fixture: ComponentFixture<ConsultPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ConsultPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 15 - 0
src/app/consult/consult.page.ts

@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-consult',
+  templateUrl: './consult.page.html',
+  styleUrls: ['./consult.page.scss'],
+})
+export class ConsultPage implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}

+ 240 - 49
src/app/tab1/tab1.page.html

@@ -264,45 +264,7 @@
         
       </ion-card-content>
     </ion-card>
-    <!-- 内容流 -->
-    <ion-list>
-      <ion-item *ngFor="let item of contentList">
-        <ion-thumbnail>
-          <img [src]="item.image">
-        </ion-thumbnail>
-        <ion-label>
-          <h2>{{item.title}}</h2>
-          <p>{{item.user}}</p>
-          <div class="like-wrapper">
-            <input class="check" type="checkbox" id="like-toggle" />
-            <label class="container" for="like-toggle">
-              <svg
-                viewBox="0 0 512 512"
-                xmlns="http://www.w3.org/2000/svg"
-                class="icon inactive"
-              >
-                <path
-                  d="M225.8 468.2l-2.5-2.3L48.1 303.2C17.4 274.7 0 234.7 0 192.8v-3.3c0-70.4 50-130.8 119.2-144C158.6 37.9 198.9 47 231 69.6c9 6.4 17.4 13.8 25 22.3c4.2-4.8 8.7-9.2 13.5-13.3c3.7-3.2 7.5-6.2 11.5-9c0 0 0 0 0 0C313.1 47 353.4 37.9 392.8 45.4C462 58.6 512 119.1 512 189.5v3.3c0 41.9-17.4 81.9-48.1 110.4L288.7 465.9l-2.5 2.3c-8.2 7.6-19 11.9-30.2 11.9s-22-4.2-30.2-11.9zM239.1 145c-.4-.3-.7-.7-1-1.1l-17.8-20c0 0-.1-.1-.1-.1c0 0 0 0 0 0c-23.1-25.9-58-37.7-92-31.2C81.6 101.5 48 142.1 48 189.5v3.3c0 28.5 11.9 55.8 32.8 75.2L256 430.7 431.2 268c20.9-19.4 32.8-46.7 32.8-75.2v-3.3c0-47.3-33.6-88-80.1-96.9c-34-6.5-69 5.4-92 31.2c0 0 0 0-.1 .1s0 0-.1 .1l-17.8 20c-.3 .4-.7 .7-1 1.1c-4.5 4.5-10.6 7-16.9 7s-12.4-2.5-16.9-7z"
-                ></path>
-              </svg>
-              <svg
-                viewBox="0 0 512 512"
-                xmlns="http://www.w3.org/2000/svg"
-                class="icon active"
-              >
-                <path
-                  d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"
-                ></path>
-              </svg>
-              <div class="checkmark"></div>
-              <span class="like-text">{{item.likes}}</span>
-            </label>
-          </div>
-          <p>{{item.comments}} Comments</p>
-          <p>{{item.tags}}</p>
-        </ion-label>
-      </ion-item>
-    </ion-list>
+
   </div>
 
   <!-- 视频页 -->
@@ -333,16 +295,44 @@
     <!-- 案例设计内容 -->
     <div class="carousel-container">
       <div class="carousel">
-          <div class="card0">Card 1</div>
-          <div class="card0">Card 2</div>
-          <div class="card0">Card 3</div>
-          <div class="card0">Card 4</div>
-          <div class="card0">Card 5</div>
-          <div class="card0">Card 6</div>
-          <div class="card0">Card 7</div>
-          <div class="card0">Card 8</div>
-          <div class="card0">Card 9</div>
-          <div class="card0">Card 10</div>
+          <div class="card0">
+            <img src="assets\img\card1.jpg" alt="">
+          </div>
+          <div class="card0">           
+            <img src="assets\img\card2.jpg" alt="">
+          </div>
+          <div class="card0">
+            <img src="assets\img\card3.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card4.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card5.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card6.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card7.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card8.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card9.jpg" alt="">
+
+          </div>
+          <div class="card0">
+            <img src="assets\img\card10.jpg" alt="">
+
+          </div>
       </div>
   </div>
     <img src="example2.jpg" alt="Example 1">
@@ -357,6 +347,207 @@
   <!-- 装修施工页 -->
   <div *ngIf="selectedSegment === 'decoration'">
     <!-- 装修施工内容 -->
+    <ion-segment style="margin-top: 10px;" [(ngModel)]="selectedCategory">
+      <ion-segment-button value="company">
+        装修公司
+      </ion-segment-button>
+      <ion-segment-button value="designers">
+        设计师
+      </ion-segment-button>
+      <ion-segment-button value="package">
+        装修套餐
+      </ion-segment-button>
+      <ion-segment-button value="materials">
+        家居建材
+      </ion-segment-button>
+    </ion-segment>
+
+    <!-- 装修公司页内容 -->
+    <div *ngIf="selectedCategory === 'company'">
+      <ion-segment scrollable [(ngModel)]="selectedCompanyCategory">
+        <ion-segment-button value="one-stop" class="fit">
+          一站式整装
+        </ion-segment-button>
+        <ion-segment-button value="chain-brand" class="fit">
+          连锁品牌
+        </ion-segment-button>
+        <ion-segment-button value="alliance" class="fit">
+          联盟商家
+        </ion-segment-button>
+        <ion-segment-button value="top-listed" class="fit">
+          上榜商家
+        </ion-segment-button>
+        <ion-segment-button value="10-year-warranty" class="fit">
+          水电质保10年以上
+        </ion-segment-button>
+      </ion-segment>
+
+      <!-- 装修公司内容部分 -->
+      <div *ngIf="selectedCompanyCategory === 'one-stop'">
+        <!-- 一站式整装内容 -->
+        <ion-list style="margin-top: 10px;">
+          <ion-item *ngFor="let item of onestopList">
+            <ion-thumbnail slot="start" >
+              <img [src]="item.image">
+            </ion-thumbnail>
+            <ion-label>
+              <h2>
+                {{item.title}}
+              </h2>
+              <p style="color: red;">
+                {{item.score}}分 {{item.price}}万元/套
+              </p>
+              <p>
+                {{item.locate}} 近期服务{{item.people}}人
+              </p>
+              <ion-chip>
+                {{item.tag1}}
+              </ion-chip>
+              <ion-chip>
+                {{item.tag2}}
+              </ion-chip>
+
+            </ion-label>
+          </ion-item>
+        </ion-list>
+      </div>
+      <div *ngIf="selectedCompanyCategory === 'chain-brand'">
+        <!-- 连锁品牌内容 -->
+      </div>
+      <div *ngIf="selectedCompanyCategory === 'alliance'">
+        <!-- 联盟商家内容 -->
+      </div>
+      <div *ngIf="selectedCompanyCategory === 'top-listed'">
+        <!-- 上榜商家内容 -->
+      </div>
+      <div *ngIf="selectedCompanyCategory === '10-year-warranty'">
+        <!-- 水电质保10年以上内容 -->
+      </div>
+    </div>
+
+    <!-- 设计师页内容 -->
+    <div *ngIf="selectedCategory === 'designers'">
+      
+      <ion-segment scrollable [(ngModel)]="selectedDesignerCategory">
+        <ion-segment-button value="full-design">
+          全案设计
+        </ion-segment-button>
+        <ion-segment-button value="100-300">
+          100-300/m
+        </ion-segment-button>
+        <ion-segment-button value="retro-modern-simple">
+          复古现代简约
+        </ion-segment-button>
+        <ion-segment-button value="preferred-designer">
+          优选设计师
+        </ion-segment-button>
+        <ion-segment-button value="top-listed-designer">
+          上榜设计师
+        </ion-segment-button>
+        <ion-segment-button value="video-call">
+          可面聊
+        </ion-segment-button>
+        <ion-segment-button value="construction">
+          可施工
+        </ion-segment-button>
+      </ion-segment>
+
+      <!-- 设计师内容部分 -->
+      <div *ngIf="selectedDesignerCategory === 'full-design'">
+        <!-- 全案设计内容 -->
+      </div>
+      <div *ngIf="selectedDesignerCategory === '100-300'">
+        <!-- 100-300/m内容 -->
+      </div>
+      <div *ngIf="selectedDesignerCategory === 'retro-modern-simple'">
+        <!-- 复古现代简约内容 -->
+      </div>
+      <div *ngIf="selectedDesignerCategory === 'preferred-designer'">
+        <!-- 优选设计师内容 -->
+      </div>
+      <div *ngIf="selectedDesignerCategory === 'top-listed-designer'">
+        <!-- 上榜设计师内容 -->
+      </div>
+      <div *ngIf="selectedDesignerCategory === 'video-call'">
+        <!-- 可面聊内容 -->
+      </div>
+      <div *ngIf="selectedDesignerCategory === 'construction'">
+        <!-- 可施工内容 -->
+      </div>
+    </div>
+
+    <!-- 装修套餐页内容 -->
+    <div *ngIf="selectedCategory === 'package'">
+      
+      <ion-segment scrollable [(ngModel)]="selectedPackageCategory">
+        <ion-segment-button value="top-listed-package">
+          上榜套餐
+        </ion-segment-button>
+        <ion-segment-button value="1000-1200">
+          1000-1200元/m
+        </ion-segment-button>
+        <ion-segment-button value="nordic-style">
+          北欧风
+        </ion-segment-button>
+        <ion-segment-button value="modern-simple-style">
+          现代简约风
+        </ion-segment-button>
+      </ion-segment>
+
+      <!-- 装修套餐内容部分 -->
+      <div *ngIf="selectedPackageCategory === 'top-listed-package'">
+        <!-- 上榜套餐内容 -->
+      </div>
+      <div *ngIf="selectedPackageCategory === '1000-1200'">
+        <!-- 1000-1200元/m内容 -->
+      </div>
+      <div *ngIf="selectedPackageCategory === 'nordic-style'">
+        <!-- 北欧风内容 -->
+      </div>
+      <div *ngIf="selectedPackageCategory === 'modern-simple-style'">
+        <!-- 现代简约风内容 -->
+      </div>
+    </div>
+
+    <!-- 家居建材页内容 -->
+    <div *ngIf="selectedCategory === 'materials'">
+      
+      <ion-segment scrollable [(ngModel)]="selectedMaterialsCategory">
+        <ion-segment-button value="decoration-materials">
+          装修建材
+        </ion-segment-button>
+        <ion-segment-button value="whole-house-customization">
+          全屋定制
+        </ion-segment-button>
+        <ion-segment-button value="finished-furniture">
+          成品家私
+        </ion-segment-button>
+        <ion-segment-button value="kitchen-bathroom-appliances">
+          厨卫电器
+        </ion-segment-button>
+        <ion-segment-button value="home-appliances">
+          家用电器
+        </ion-segment-button>
+      </ion-segment>
+
+      <!-- 家居建材内容部分 -->
+      <div *ngIf="selectedMaterialsCategory === 'decoration-materials'">
+        <!-- 装修建材内容 -->
+      </div>
+      <div *ngIf="selectedMaterialsCategory === 'whole-house-customization'">
+        <!-- 全屋定制内容 -->
+      </div>
+      <div *ngIf="selectedMaterialsCategory === 'finished-furniture'">
+        <!-- 成品家私内容 -->
+      </div>
+      <div *ngIf="selectedMaterialsCategory === 'kitchen-bathroom-appliances'">
+        <!-- 厨卫电器内容 -->
+      </div>
+      <div *ngIf="selectedMaterialsCategory === 'home-appliances'">
+        <!-- 家用电器内容 -->
+      </div>
+    </div>
+
     <img src="example3.jpg" alt="Example 3">
   </div>
 

+ 6 - 1
src/app/tab1/tab1.page.scss

@@ -11,6 +11,10 @@ ion-thumbnail {
   border-radius: 50%;
 }
 
+.fit {
+  width: auto;
+}
+
 h2 {
   font-size: 1.2em;
   margin-bottom: 5px;
@@ -335,4 +339,5 @@ ion-card img {
   width: 100%;
   height: 200px;
   object-fit: cover;
-}
+}
+

+ 60 - 3
src/app/tab1/tab1.page.ts

@@ -8,11 +8,19 @@ import Swiper from 'swiper';
   templateUrl: 'tab1.page.html',
   styleUrls: ['tab1.page.scss']
 })
+
 export class Tab1Page implements OnInit {
 
   selectedSegment: string = 'explore';
   @ViewChild("slide1Comp") slide1Comp:ElementRef |undefined
 
+  selectedCategory: string = 'company'; // 默认选中装修公司页
+  selectedCompanyCategory: string = 'one-stop'; // 默认选中一站式整装
+
+  selectedDesignerCategory: string = 'full-design'; // 默认选中全案设计
+  selectedPackageCategory: string = 'top-listed-package'; // 默认选中上榜套餐
+  selectedMaterialsCategory: string = 'decoration-materials'; // 默认选中装修建材
+  
   constructor() {}
 
   ngOnInit() {
@@ -47,9 +55,58 @@ export class Tab1Page implements OnInit {
   }
 
   // 在此处定义和初始化数据
-  contentList = [
-    { image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', title: '示例标题1', user: '用户A', likes: 10, comments: 5, tags: '标签1, 标签2' },
-    { image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', title: '示例标题2', user: '用户B', likes: 20, comments: 8, tags: '标签3, 标签4' },
+  onestopList = [
+    { 
+      image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', 
+      title: '江西创艺装饰', 
+      score: 4.6, 
+      price: 10-15, 
+      locate: '红谷滩区', 
+      people: 109,
+      tag1: '十年老店' ,
+      tag2: '透明报价' 
+
+    },
+    { 
+      image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', 
+      title: '南昌雅美居装饰', 
+      score: 4.4, 
+      price: 8-13, 
+      locate: '红谷滩区', 
+      people: 98,
+      tag1: '十年老店' ,
+      tag2: '透明报价' 
+    },
+    { 
+      image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', 
+      title: '江西海峰装饰', 
+      score: 4.2, 
+      price: 7-12, 
+      locate: '东湖区', 
+      people: 22,
+      tag1: '十年老店' ,
+      tag2: '透明报价' 
+    },
+    { 
+      image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', 
+      title: '南昌创美乐居装饰', 
+      score: 4.1, 
+      price: 7-12, 
+      locate: '南昌县', 
+      people: 11,
+      tag1: '十年老店',
+      tag2: '毛坯装修' 
+    },
+    { 
+      image: 'https://img.zcool.cn/community/01df555d65edcea8012187f4e41c2f.jpg@2o.jpg', 
+      title: '圣都整装南昌分公司', 
+      score: 4.3, 
+      price: 15-20, 
+      locate: '青山湖区', 
+      people: 22,
+      tag1: '十年老店',
+      tag2: '毛坯装修' 
+    },
     // 添加更多示例数据
   ];
   

+ 3 - 1
src/app/tab2/tab2.page.html

@@ -216,7 +216,9 @@
         </div>
         <div class="name-container">
           <ion-card-title class="title">{{ designer.username }}</ion-card-title>
-          <ion-button fill="clear" slot="end">关注</ion-button>
+          <ion-button fill="clear" slot="end" class="custom-button" (click)="toggleFollow()">
+            {{ isFollowing ? '已关注' : '关注' }}
+          </ion-button>
         </div>
       </ion-card-header>
       <ion-card-content>

+ 0 - 3
src/app/tab2/tab2.page.scss

@@ -82,7 +82,4 @@ ion-button {
 ion-button:hover {
   background-color: #ffd4f0; /* 鼠标悬停时修改背景颜色 */
   color: #000000; /* 鼠标悬停时修改文字颜色 */
-}
-.custom-button {
-  font-size: 20px; /* 修改按钮内文字的字体大小 */
 }

+ 6 - 2
src/app/tab2/tab2.page.ts

@@ -357,7 +357,8 @@ export class Tab2Page {
     // 添加更多设计师信息
   ];
   selectedDesignerSegment: string = 'designer';
-
+  isFollowing: boolean = false;
+  
   constructor(private sanitizer: DomSanitizer) {}
   selectedCompany: any;
 
@@ -368,7 +369,10 @@ toggleDetails(company: any) {
     this.selectedCompany = company;
   }
 }
-
+// 在组件类中实现一个方法来切换关注状态
+toggleFollow() {
+  this.isFollowing = !this.isFollowing;
+}
   formatHtmlContent(content: string): SafeHtml {
     const formattedContent = content.replace(/\n/g, '<br>');
     return this.sanitizer.bypassSecurityTrustHtml(formattedContent);

+ 1 - 1
src/app/tab3/tab3.page.html

@@ -11,7 +11,7 @@
 </ion-header>
 
 <ion-content>
-<div style="background-color: rgb(0, 132, 255);">
+<div style="background-color: rgb(245, 246, 246);">
   <ion-searchbar placeholder="输入关键字搜索"></ion-searchbar>
   <ion-card class="card">
     <ion-card-header >

+ 1 - 1
src/app/tab3/tab3.page.scss

@@ -14,7 +14,7 @@ ion-avatar {
   width: 365px;
   height: 250px;
   background-color: #4158D0;
-  background-image: linear-gradient(43deg, #41d0d0 0%, #0c94d8 46%, #baff70 100%);
+  background-image: linear-gradient(43deg, #41d0d0 0%, #0c6bd8 46%, #baff70 100%);
   border-radius: 8px;
   color: white;
   overflow: hidden;

binární
src/assets/img/card1.jpg


binární
src/assets/img/card10.jpg


binární
src/assets/img/card2.jpg


binární
src/assets/img/card3.jpg


binární
src/assets/img/card4.jpg


binární
src/assets/img/card5.jpg


binární
src/assets/img/card6.jpg


binární
src/assets/img/card7.jpg


binární
src/assets/img/card8.jpg


binární
src/assets/img/card9.jpg