布丁撞奶茶 6 月之前
父節點
當前提交
b990fe4a8c

+ 12 - 0
travel-app/src/app/edittag/edittag.component.html

@@ -0,0 +1,12 @@
+<ion-item
+  [ngClass]="{ clicked: clicked, loading: loading }"
+  (click)="onClick()"
+  [disabled]="disabled"
+>
+  <div class="card-content">
+    <ion-icon [name]="iconName"></ion-icon>
+    <ion-label class="card-label">{{ label }}</ion-label>
+  </div>
+  <ion-spinner *ngIf="loading" name="crescent" color="primary"></ion-spinner>
+  <!-- 显示加载指示 -->
+</ion-item>

+ 68 - 0
travel-app/src/app/edittag/edittag.component.scss

@@ -0,0 +1,68 @@
+@keyframes rippleEffect {
+  0% {
+    background-color: darken(#fefae0, 10%);
+    transform: scale(1);
+  }
+  100% {
+    background-color: #fefae0;
+    transform: scale(1.5);
+  }
+}
+
+ion-item {
+  --background: #fefae0;
+  --color: #ccd5ae;
+  --min-height: 80px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  transition: color 0.3s ease;
+  position: relative;
+  overflow: hidden;
+  font-family: "Poppins", sans-serif; /* 使用 Poppins 字体 */
+
+  &.clicked::before {
+    content: "";
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 0;
+    height: 0;
+    background-color: darken(#fefae0, 10%);
+    border-radius: 50%;
+    transform: translate(-50%, -50%) scale(0);
+    animation: rippleEffect 0.6s forwards;
+  }
+
+  &.loading {
+    pointer-events: none; /* 禁止交互 */
+  }
+
+  &:hover,
+  &:active {
+    --color: #ccd5ae;
+  }
+
+  .card-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 8px; /* 图标和文字之间的间距 */
+  }
+
+  ion-icon {
+    font-size: 24px; /* Adjust icon size as needed */
+  }
+
+  .card-label {
+    font-size: 14px; /* Adjust text size as needed */
+    line-height: 1.5;
+  }
+
+  ion-spinner {
+    margin-top: 8px;
+  }
+}

+ 22 - 0
travel-app/src/app/edittag/edittag.component.spec.ts

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

+ 87 - 0
travel-app/src/app/edittag/edittag.component.ts

@@ -0,0 +1,87 @@
+import {
+  Component,
+  OnInit,
+  Input,
+  Output,
+  EventEmitter,
+  ChangeDetectionStrategy,
+  ChangeDetectorRef,
+  OnDestroy,
+} from '@angular/core';
+import {
+  IonButton,
+  IonContent,
+  IonIcon,
+  IonCard,
+  IonCardContent,
+  IonLabel,
+  IonItem,
+  IonSpinner,
+} from '@ionic/angular/standalone';
+import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router';
+import { Subject, takeUntil } from 'rxjs';
+
+@Component({
+  selector: 'app-edittag',
+  templateUrl: './edittag.component.html',
+  styleUrls: ['./edittag.component.scss'],
+  standalone: true,
+  imports: [
+    IonCardContent,
+    IonCard,
+    IonIcon,
+    IonContent,
+    IonButton,
+    IonLabel,
+    IonItem,
+    CommonModule,
+    IonSpinner,
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush, // 使用按需变更检测
+})
+export class EdittagComponent implements OnInit {
+  @Input() iconName: string = '';
+  @Input() label: string = '';
+  @Input() route: string = ''; // The route to navigate to on click
+  @Input() disabled: boolean = false; // 是否禁用按钮
+  @Input() loading: boolean = false; // 加载状态
+  @Output() clicked = new EventEmitter<void>(); // 点击事件
+
+  private click$ = new Subject<void>();
+  private destroy$ = new Subject<void>();
+
+  constructor(private router: Router, private cd: ChangeDetectorRef) {}
+
+  ngOnInit(): void {
+    this.click$.pipe(takeUntil(this.destroy$)).subscribe(() => {
+      if (!this.disabled && !this.loading) {
+        this.clicked.emit(); // 触发点击事件
+        this.navigate();
+      }
+    });
+  }
+
+  ngOnDestroy(): void {
+    this.destroy$.next();
+    this.destroy$.complete();
+  }
+
+  onClick(): void {
+    if (!this.disabled && !this.loading) {
+      this.loading = true;
+      this.cd.markForCheck(); // 手动触发变更检测
+      this.click$.next();
+      setTimeout(() => {
+        this.loading = false;
+        this.cd.markForCheck(); // 手动触发变更检测
+      }, 300); // 模拟加载时间
+    }
+  }
+
+  private navigate(): void {
+    if (this.route) {
+      this.router.navigate([this.route]);
+    }
+  }
+}

+ 91 - 14
travel-app/src/app/tab1/tab1.page.html

@@ -1,20 +1,97 @@
 <ion-header [translucent]="true">
-  <ion-toolbar>
-    <ion-title>
-      智韵云游————南昌
-    </ion-title>
+  <ion-toolbar class="custom-toolbar">
+    <!-- 天气组件 -->
+    <app-weather [temp]="21" [position]="'南昌'"></app-weather>
+
+    <!-- 搜索框 -->
+    <ion-searchbar
+      slot="end"
+      placeholder="搜索景点"
+      class="searchbar"
+      value="搜索景点"
+    ></ion-searchbar>
   </ion-toolbar>
-  <ion-searchbar color="light" placeholder="Light" showCancelButton="focus" class="custom">
-  
-  </ion-searchbar>
 </ion-header>
 
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Tab 1</ion-title>
-    </ion-toolbar>
-  </ion-header>
+<ion-content>
+  <div class="main-container">
+    <ion-content>
+      <div class="image-container">
+        <img
+          src="../../assets/images/tengwangge.png"
+          alt="Descriptive Image Alt Text"
+        />
+        <button class="play-button">畅玩南昌</button>
+      </div>
 
-  <app-explore-container name="Tab 1 page" (click)="f()"></app-explore-container>
+      <div class="content-container">
+        <ion-grid>
+          <ion-row>
+            <!-- 第一行 -->
+            <ion-col size="4">
+              <app-edittag
+                iconName="people"
+                label="一起游"
+                route="/play-together"
+              ></app-edittag>
+            </ion-col>
+            <ion-col size="4">
+              <app-edittag
+                iconName="compass"
+                label="景点推荐"
+                (click)="goViewplacePage()"
+              ></app-edittag>
+            </ion-col>
+            <ion-col size="4">
+              <app-edittag
+                iconName="help-circle"
+                label="一键求助"
+                route="/get-help"
+              ></app-edittag>
+            </ion-col>
+          </ion-row>
+          <ion-row>
+            <!-- 第二行 -->
+            <ion-col size="4">
+              <app-edittag
+                iconName="people"
+                label="一起游玩"
+                route="/play-with-others"
+              ></app-edittag>
+            </ion-col>
+            <ion-col size="4">
+              <app-edittag
+                iconName="calendar"
+                label="活动日历"
+                route="/event-calendar"
+              ></app-edittag>
+            </ion-col>
+            <ion-col size="4">
+              <app-edittag
+                iconName="chatbubbles"
+                label="游客互动"
+                route="/visitor-interaction"
+              ></app-edittag>
+            </ion-col>
+          </ion-row>
+        </ion-grid>
+      </div>
+      <ion-content>
+        <div class="hot-articles-container">
+          <h2>热门文章</h2>
+          <ion-list>
+            <ion-item *ngFor="let article of articles">
+              <ion-label>
+                <h3>{{ article.title }}</h3>
+                <p>{{ article.summary }}</p>
+              </ion-label>
+              <ion-button fill="clear" slot="end">
+                <ion-icon name="arrow-forward"></ion-icon>
+              </ion-button>
+            </ion-item>
+          </ion-list>
+        </div>
+      </ion-content>
+    </ion-content>
+  </div>
 </ion-content>

+ 176 - 16
travel-app/src/app/tab1/tab1.page.scss

@@ -1,18 +1,178 @@
-/* Scoped components require higher specificity to customize */
-ion-searchbar.custom {
-    --background: #19422d;
-    --color: #fff;
-    --placeholder-color: #c83131;
-    --icon-color: #fff;
-    --clear-button-color: #fff;
-  
-    --border-radius: 4px;
+// 工具栏样式
+ion-toolbar {
+  --background: transparent;
+  --min-height: 60px;
+  padding: 8px 16px;
+}
+
+.custom-toolbar {
+  --background: transparent;
+  background-color: #fefae0;
+  padding: 0 16px;
+}
+
+// 搜索框样式
+.weather-col {
+  --border-radius: 10px;
+  width: 100%;
+  max-width: 300px;
+}
+
+.searchbar {
+  --background: rgba(255, 255, 255, 0.7);
+  --border-radius: 20px;
+  --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  --padding-start: 16px;
+  --padding-end: 16px;
+  --placeholder-color: #333;
+  width: 200px;
+  height: 20px;
+}
+
+// 内容容器样式
+.main-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  background-color: #faedcd;
+  padding: 16px;
+  overflow: hidden; // 确保内容不会超出圆角区域
+}
+
+.content-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%; // 使用100%宽度而不是fit-content
+  border-radius: 20px; // 设置圆角
+  background-color: rgba(255, 255, 255, 0.2); /* 半透明的白色背景 */
+  padding: 1px; /* 减少内边距以避免间隔过大 */
+  box-shadow: none;
+  margin: 0 auto;
+  // 添加额外的样式以确保圆角显示
+  position: relative;
+  z-index: 1; // 如果需要的话,提高z-index
+  &::after {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    border-radius: 20px;
+    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); // 可选,添加阴影效果
+    z-index: -1; // 确保它位于内容下方
   }
-  
-  ion-searchbar.ios.custom {
-    --cancel-button-color: #19422d;
+}
+
+/* image-with-button.component.scss */
+.image-container {
+  position: relative;
+  width: 100%;
+  max-width: 500px;
+  margin: auto;
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-around;
+  padding: 10px;
+
+  img {
+    width: 100%;
+    border-radius: 8px;
+    display: block;
   }
-  
-  ion-searchbar.md.custom {
-    --cancel-button-color: #336959;
-  }
+
+  .play-button {
+    position: absolute;
+    bottom: 30px;
+    right: 30px;
+    padding: 10px 20px;
+    background-color: rgba(0, 0, 0, 0.15);
+    color: white;
+    border: none;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 16px;
+
+    &:hover {
+      background-color: rgba(0, 0, 0, 0.3);
+    }
+  }
+}
+
+// 特性卡片样式
+.feature-box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 0;
+  background-color: transparent;
+}
+
+.card-section {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 16px;
+  width: 100%;
+}
+
+.app-feature-card {
+  --background: #fefae0;
+  --color: #ccd5ae;
+  --min-height: 80px;
+  --border-radius: 12px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  transition: color 0.3s ease, box-shadow 0.3s ease;
+  position: relative;
+  overflow: hidden;
+  font-family: "Poppins", sans-serif;
+  border-radius: var(--border-radius);
+  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  width: 100%;
+}
+
+.app-feature-card:hover,
+.app-feature-card:active {
+  --color: #ccd5ae;
+  box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
+}
+
+.app-feature-card .card-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 16px;
+}
+
+.app-feature-card ion-icon {
+  font-size: 24px;
+}
+
+.app-feature-card .card-label {
+  font-size: 14px;
+  line-height: 1.5;
+}
+// hot-articles.component.scss
+.hot-articles-container {
+  padding: 16px;
+
+  h2 {
+    margin-bottom: 16px;
+  }
+
+  ion-list {
+    background: none;
+  }
+
+  ion-item {
+    --background: rgba(255, 255, 255, 0.9);
+    border-radius: 8px;
+    margin-bottom: 8px;
+  }
+}

+ 96 - 9
travel-app/src/app/tab1/tab1.page.ts

@@ -1,22 +1,109 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonSearchbar,NavController } from '@ionic/angular/standalone';
+import { NavController } from '@ionic/angular';
+
+import {
+  IonHeader,
+  IonToolbar,
+  IonTitle,
+  IonContent,
+  IonButton,
+  IonButtons,
+  IonIcon,
+  IonSearchbar,
+  IonCard,
+  IonCardHeader,
+  IonCardContent,
+  IonCardTitle,
+  IonList,
+  IonItem,
+  IonLabel,
+  IonGrid,
+  IonRow,
+  IonCol,
+  IonThumbnail,
+  IonAvatar,
+  IonMenuButton,
+  IonCardSubtitle,
+  IonTabs,
+  IonTabBar,
+  IonTabButton,
+} from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
+import { EdittagComponent } from '../edittag/edittag.component';
+import { WeatherComponent } from '../weather/weather.component'; // 路径根据实际情况调整
+import { CommonModule } from '@angular/common';
+import { addIcons } from 'ionicons';
+import {
+  people,
+  compass,
+  helpCircle, // 注意:`help-circle` 在 TypeScript 中是 `helpCircle`
+  calendar,
+  chatbubbles,
+} from 'ionicons/icons';
+import { Router } from '@angular/router';
 
 @Component({
   selector: 'app-tab1',
   templateUrl: 'tab1.page.html',
   styleUrls: ['tab1.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,IonSearchbar]
+  imports: [
+    IonTabButton,
+    IonTabBar,
+    IonHeader,
+    IonToolbar,
+    IonTitle,
+    IonContent,
+    ExploreContainerComponent,
+    IonButton,
+    IonButtons,
+    IonIcon,
+    IonSearchbar,
+    IonCard,
+    IonCardHeader,
+    IonCardContent,
+    IonCardTitle,
+    IonList,
+    IonItem,
+    IonLabel,
+    IonGrid,
+    IonRow,
+    IonCol,
+    IonThumbnail,
+    IonAvatar,
+    IonMenuButton,
+    IonCardSubtitle,
+    IonTabs,
+    EdittagComponent,
+    CommonModule,
+    WeatherComponent,
+  ],
 })
 export class Tab1Page {
-
-  constructor(private navCtrl:NavController) {
-
+  constructor(private router: Router) {
+    addIcons({
+      people: people,
+      compass: compass,
+      'help-circle': helpCircle, // 使用破折号命名
+      calendar: calendar,
+      chatbubbles: chatbubbles,
+    });
   }
-
-  f(){
-this.navCtrl.navigateForward('tabs/Container');
-
+  articles = [
+    { title: '探索南昌的历史', summary: '南昌的历史悠久,让我们一起探索...' },
+    { title: '最佳旅游季节', summary: '了解在什么时间去南昌旅游最合适...' },
+    { title: '当地美食推荐', summary: '南昌的特色美食,不容错过的美味...' },
+  ];
+  images = [
+    {
+      src: 'C:/Users/布丁撞奶茶/Desktop/工作室作业/代码/s202226701012/workspace/travel-app/src/app/img/tengwangge.png',
+      alt: 'Image 1',
+    },
+    // { src: 'path/to/image2.jpg', alt: 'Image 2' },
+    // { src: 'path/to/image3.jpg', alt: 'Image 3' },
+    // 添加更多图片路径和替代文本
+  ];
+  goViewplacePage() {
+    this.router.navigate(['/tabs/viewplace']);
   }
 }

+ 15 - 0
travel-app/src/app/weather/weather.component.html

@@ -0,0 +1,15 @@
+<div class="weather-container">
+  <div class="weather-icon">
+    <img
+      [ngClass]="iconClass"
+      src="https://assets.msn.cn/weathermapdata/1/static/weather/Icons/taskbar_v10/Condition_Card/MostlyCloudyDayV2.svg"
+      alt="Mostly Cloudy Weather"
+      (mouseenter)="onMouseEnter()"
+      (mouseleave)="onMouseLeave()"
+    />
+  </div>
+  <div class="weather-info">
+    <span class="temperature">{{ temp }}°C</span>
+    <span class="position">{{ position }}</span>
+  </div>
+</div>

+ 37 - 0
travel-app/src/app/weather/weather.component.scss

@@ -0,0 +1,37 @@
+.weather-container {
+  display: flex; /* 使用flexbox布局 */
+  align-items: center; /* 垂直居中对齐 */
+  width: 200px; /* 控制整体宽度 */
+  height: 80px; /* 控制整体高度 */
+
+  overflow: hidden; /* 隐藏超出部分 */
+}
+
+.weather-icon {
+  flex: 0 0 auto; /* 图标固定宽度 */
+  width: 70px; /* 控制图标的宽度 */
+  height: 70px; /* 图标高度 */
+}
+
+.weather-icon img {
+  width: 100%; /* 图标自适应宽度 */
+  height: auto; /* 保持图标的纵横比 */
+}
+
+.weather-info {
+  flex: 1; /* 文字区域自适应剩余空间 */
+  padding-left: 10px; /* 图标与文字之间的间距 */
+  display: flex; /* 使用flexbox布局 */
+  flex-direction: column; /* 文字内容垂直排列 */
+  justify-content: center; /* 垂直居中对齐 */
+}
+
+.temperature,
+.position {
+  color: black; /* 文字颜色 */
+  font-size: 16px; /* 控制字体大小 */
+}
+
+.position {
+  font-size: 14px; /* 可选:控制位置文字的字体大小 */
+}

+ 22 - 0
travel-app/src/app/weather/weather.component.spec.ts

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

+ 31 - 0
travel-app/src/app/weather/weather.component.ts

@@ -0,0 +1,31 @@
+import { Component, OnInit, Input, ElementRef, ViewChild } from '@angular/core';
+import { IonContent, IonIcon } from '@ionic/angular/standalone';
+import { CommonModule } from '@angular/common';
+import { addIcons } from 'ionicons';
+import { sunny, cloudy, rainy, snow } from 'ionicons/icons';
+
+@Component({
+  selector: 'app-weather',
+  templateUrl: './weather.component.html',
+  styleUrls: ['./weather.component.scss'],
+  standalone: true,
+  imports: [IonIcon, IonContent, CommonModule],
+})
+export class WeatherComponent implements OnInit {
+  @Input() position: string = '南昌';
+  @Input() temp: number = 21;
+
+  iconClass: string = 'weather-icon';
+
+  onMouseEnter(): void {
+    this.iconClass = 'weather-icon hover';
+  }
+
+  onMouseLeave(): void {
+    this.iconClass = 'weather-icon';
+  }
+  ngOnInit(): void {}
+  constructor() {
+    addIcons({ sunny, cloudy, rainy, snow });
+  }
+}

二進制
travel-app/src/assets/img/tengwangge.png