Kaynağa Gözat

feat: demo bird

未来全栈 12 saat önce
ebeveyn
işleme
be05c8a641

+ 44 - 44
src/app/app.routes.ts

@@ -5,54 +5,54 @@ export const routes: Routes = [
     path: '',
     loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
   },
-   // 向量:文本特征向量
-   {
-      path: "text/embed",
-      loadComponent: () => import('../modules/text/page-text-embed/page-text-embed.component').then(m => m.PageTextEmbedComponent),
-   },
+  // 向量:文本特征向量
+  {
+    path: "text/embed",
+    loadComponent: () => import('../modules/text/page-text-embed/page-text-embed.component').then(m => m.PageTextEmbedComponent),
+  },
   // 知识库:杭州市人才政策知识库
   {
-      path: "story/hangzhou",
-      loadComponent: () => import('../modules/story/page-hangzhou/page-hangzhou.component').then(m => m.PageHangzhouComponent),
-      runGuardsAndResolvers: "always",
+    path: "story/hangzhou",
+    loadComponent: () => import('../modules/story/page-hangzhou/page-hangzhou.component').then(m => m.PageHangzhouComponent),
+    runGuardsAndResolvers: "always",
   },
   // 向量:面部特征向量
+  // {
+  //     path: "face/feat68",
+  //     loadComponent: () => import('../modules/face/page-feat68/page-feat68.component').then(m => m.PageFeat68Component),
+  //     runGuardsAndResolvers: "always",
+  // },
+  // 聊天模块
+  {
+    path: "chat/session/role/:roleId",
+    loadComponent: () => import('./test-chat-panel/test-chat-panel.component').then(m => m.TestChatPanelComponent),
+    runGuardsAndResolvers: "always",
+  },
+  {
+    path: "chat/session/chat/:chatId",
+    loadComponent: () => import('./test-chat-panel/test-chat-panel.component').then(m => m.TestChatPanelComponent),
+    runGuardsAndResolvers: "always",
+  },
+  {
+    path: 'chat/pro/chat/:chatId',
+    redirectTo: '/chat/session/chat/:chatId',
+    pathMatch: 'full'
+  },
+  // 流程任务模块
+  {
+    path: "flow/editor/new",
+    loadComponent: () => import('../modules/flow/comp-flow-editor/comp-flow-editor.component').then(m => m.CompFlowEditorComponent),
+    runGuardsAndResolvers: "always",
+  },
+  {
+    path: "flow/test",
+    loadComponent: () => import('../modules/flow/page-flow-test/page-flow-test.component').then(m => m.PageFlowTestComponent),
+    runGuardsAndResolvers: "always",
+  },
+  // 测试任务模块
   {
-      path: "face/feat68",
-      loadComponent: () => import('../modules/face/page-feat68/page-feat68.component').then(m => m.PageFeat68Component),
-      runGuardsAndResolvers: "always",
-  },
-   // 聊天模块
-   {
-      path: "chat/session/role/:roleId",
-      loadComponent: () => import('./test-chat-panel/test-chat-panel.component').then(m => m.TestChatPanelComponent),
-      runGuardsAndResolvers: "always",
-  },
-  {
-      path: "chat/session/chat/:chatId",
-      loadComponent: () => import('./test-chat-panel/test-chat-panel.component').then(m => m.TestChatPanelComponent),
-      runGuardsAndResolvers: "always",
-  },
-  {
-      path: 'chat/pro/chat/:chatId',
-      redirectTo: '/chat/session/chat/:chatId',
-      pathMatch: 'full'
-    },
-    // 流程任务模块
-    {
-        path: "flow/editor/new",
-        loadComponent: () => import('../modules/flow/comp-flow-editor/comp-flow-editor.component').then(m => m.CompFlowEditorComponent),
-        runGuardsAndResolvers: "always",
-    },
-    {
-      path: "flow/test",
-      loadComponent: () => import('../modules/flow/page-flow-test/page-flow-test.component').then(m => m.PageFlowTestComponent),
-      runGuardsAndResolvers: "always",
-  },
-    // 测试任务模块
-    {
-      path: "task/test",
-      loadComponent: () => import('../modules/task/page-test-completion/page-test-completion.component').then(m => m.PageTestCompletionComponent),
-      runGuardsAndResolvers: "always",
+    path: "task/test",
+    loadComponent: () => import('../modules/task/page-test-completion/page-test-completion.component').then(m => m.PageTestCompletionComponent),
+    runGuardsAndResolvers: "always",
   }
 ];

+ 12 - 0
src/app/tabs/tabs.routes.ts

@@ -38,6 +38,18 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../../modules/demo/page-training-plan/page-training-plan.component').then((m) => m.PageTrainingPlanComponent),
       },
+      {
+        path: 'demo/bird/list',
+        canActivate: [TokenGuard],
+        loadComponent: () =>
+          import('../../modules/demo/bird/page-bird-list/page-bird-list.component').then((m) => m.PageBirdListComponent),
+      },
+      {
+        path: 'demo/bird/display/:birdId',
+        canActivate: [TokenGuard],
+        loadComponent: () =>
+          import('../../modules/demo/bird/page-bird-display/page-bird-display.component').then((m) => m.PageBirdDisplayComponent),
+      },
     ],
   },
   {

+ 171 - 0
src/modules/demo/bird/README.md

@@ -0,0 +1,171 @@
+
+``` bash
+mkdir -p src/modules/demo/bird
+ng g component --standalone page-bird-list
+ng g component --standalone page-bird-display
+```
+
+- 地址
+    - tabs/demo/bird/list
+    - tabs/demo/bird/display
+``` ts
+{
+        path: 'demo/bird/list',
+        canActivate: [TokenGuard],
+        loadComponent: () =>
+          import('../../modules/demo/bird/page-bird-list/page-bird-list.component').then((m) => m.PageBirdListComponent),
+      },
+      {
+        path: 'demo/bird/display/:birdId',
+        canActivate: [TokenGuard],
+        loadComponent: () =>
+          import('../../modules/demo/bird/page-bird-display/page-bird-display.component').then((m) => m.PageBirdListComponent),
+      },
+```
+
+- 思考鸟类科普的列表页面,以及鸟类的范式数据
+    - 参考:http://git.fmode.cn:3000/ljf123/ai-sport/src/master/docs/schema.md
+        - 设计数据范式
+
+设定要求 您是一名专业的数据库工程师,熟悉PostgreSQL和ParseServer。 请注意表名用大驼峰,字段小驼峰。 有预留字段:objectId、updatedAt、createdAt。 关于ParseServer中数据类的描述,字段的主要类型有: String => String Number => Number Bool => bool Array => JSON Array Object => JSON Object Date => Date File => Parse.File Pointer => other Parse.Object // 指针类型的字段不需要用xxxId形式命名,直接写xxx即可。 Relation => Parse.Relation Null => null GeoPoint => {latitude: 40.0, longitude: -30.0}
+
+项目需求 鸟类专业知识科普的AI应用,鸟类表,鸟类的分类表,以及鸟类专业知识的应用经验,设计以上两张表。
+期中鸟类表尽可能有丰富的参数字段,还需要有整体的描述字段
+
+输出结果(UML类图) 请您帮我用plantuml的类图描述设计好的几张表及其关系
+
+输出结果(信息结构图) 请您帮我用markmap格式表示上面的信息结构图
+
+输出结果(SQL语句) 请您帮我用sql格式给我建表语句和测试数据插入语句,注意字段请使用小驼峰用""引起来。
+
+``` markdown
+以下是根据您的需求设计的PostgreSQL/ParseServer数据库表结构,使用PlantUML类图表示:
+
+```plantuml
+@startuml BirdDatabaseSchema
+
+class Bird {
+  + objectId: String [PK]
+  + createdAt: Date
+  + updatedAt: Date
+  
+  + commonName: String
+  + scientificName: String
+  + description: String
+  + conservationStatus: String
+  + avgWeight: Number
+  + avgLength: Number
+  + wingspan: Number
+  + lifespan: Number
+  + diet: String
+  + habitat: String
+  + migrationPattern: String
+  + breedingSeason: String
+  + clutchSize: Number
+  + incubationPeriod: Number
+  + vocalization: String
+  + identificationTips: String
+  + behaviorNotes: String
+  + distributionRange: String
+  + similarSpecies: JSON Array
+  + images: JSON Array<Parse.File>
+  + thumbnail: Parse.File
+  + location: GeoPoint
+  + taxonomy: Pointer<Taxonomy>
+  + isEndangered: bool
+  + isExtinct: bool
+  + featured: bool
+}
+
+class Taxonomy {
+  + objectId: String [PK]
+  + createdAt: Date
+  + updatedAt: Date
+  
+  + kingdom: String
+  + phylum: String
+  + class: String
+  + order: String
+  + family: String
+  + genus: String
+  + species: String
+  + subspecies: String
+  + scientificName: String
+  + description: String
+  + characteristics: JSON Array
+  + evolutionaryHistory: String
+  + relatedTaxa: JSON Array
+}
+
+class BirdExpertise {
+  + objectId: String [PK]
+  + createdAt: Date
+  + updatedAt: Date
+  
+  + title: String
+  + content: String
+  + author: String
+  + experienceLevel: String
+  + applicationScenarios: JSON Array
+  + bestPractices: String
+  + equipmentRecommendations: JSON Array
+  + seasonalityTips: String
+  + locationTips: String
+  + relatedBirds: JSON Array<Pointer<Bird>>
+  + featuredImage: Parse.File
+  + isVerified: bool
+  + references: JSON Array
+}
+
+Bird "1" -- "1" Taxonomy : belongs to >
+BirdExpertise "n" -- "n" Bird : references >
+
+note top of Bird: 鸟类主表\n包含全面的鸟类信息\n包括生态特征、形态特征\n和行为特征等丰富字段
+
+note bottom of Taxonomy: 鸟类分类表\n按照生物分类学标准\n记录鸟类的分类信息
+
+note right of BirdExpertise: 鸟类专业知识表\n记录观鸟技巧、研究经验\n和保护实践等专业知识
+
+@enduml
+```
+
+设计说明:
+1. **Bird表**:包含全面的鸟类信息,包括:
+   - 基础信息(名称、描述)
+   - 物理特征(体重、长度、翼展)
+   - 生态信息(食性、栖息地)
+   - 行为特征(迁徙模式、繁殖季节)
+   - 多媒体资料(图片、缩略图)
+   - 地理位置信息
+   - 分类关联(指向Taxonomy表)
+
+2. **Taxonomy表**:按照生物分类学标准设计,包含:
+   - 完整的分类层级(界门纲目科属种)
+   - 分类特征描述
+   - 进化历史信息
+
+3. **BirdExpertise表**:存储专业知识应用经验,包括:
+   - 专业知识内容
+   - 应用场景
+   - 最佳实践
+   - 相关鸟类关联
+   - 经验等级和验证状态
+
+关系说明:
+- 一只鸟类对应一个分类条目(1:1)
+- 一个专业知识可以关联多个鸟类,反之亦然(n:m)
+```
+
+- 根据表结构,创建测试数据,并进行导入
+
+> {{ncloud.ts复制}}{{UML描述复制}}请您根据以上三张表的内容,帮我生成大量真实鸟类的数据作为测试。帮我用CloudObject实现导入函数将三类数据的数组保存。
+
+
+- 美化每个页面
+    - 数据结构,还有CloudObject .get取属性的用法描述清晰
+    - list页面内容复制
+    - 指导AI用ionic angular 完成页面美化工作
+
+- 美化鸟类详情页
+> 以上是鸟信息结构,请帮我用ionic angular美化鸟类展示的详情页。其中CloudObject需要用.get获取属性。
+> 以下是我已经完成的基本结构

+ 181 - 0
src/modules/demo/bird/import-bird-data.ts

@@ -0,0 +1,181 @@
+import { CloudObject } from "src/lib/ncloud";
+
+// 鸟类测试数据
+const testBirds = [
+    {
+        commonName: "红腹锦鸡",
+        scientificName: "Chrysolophus pictus",
+        description: "又名金鸡,中型鸡类,体长59-110厘米。尾特长,约38-42厘米。雄鸟羽色华丽,头具金黄色丝状羽冠,上体除上背浓绿色外,其余为金黄色,后颈被有橙棕色而缀有黑边的扇状羽,形成披肩状。下体深红色,尾羽黑褐色,满缀以桂黄色斑点。",
+        conservationStatus: "无危",
+        avgWeight: 0.8,
+        avgLength: 90,
+        wingspan: 65,
+        lifespan: 10,
+        diet: "杂食性,主要以植物的叶、芽、花、果实和种子为食,也吃小麦、大豆、玉米、四季豆等农作物。此外也吃甲虫、蠕虫、双翅目和鳞翅目昆虫等动物性食物。",
+        habitat: "常栖息于海拔500-2500米的阔叶林、针阔叶混交林和林缘疏林灌丛地带",
+        migrationPattern: "留鸟",
+        breedingSeason: "4-6月",
+        clutchSize: 8,
+        incubationPeriod: 22,
+        vocalization: "繁殖期间雄鸟常发出'cha、cha'的叫声和'cha-cha-cha'的连续叫声",
+        identificationTips: "雄鸟羽色艳丽,具有金色羽冠和红色腹部;雌鸟通体棕黄,密布黑褐色虫蠹状斑",
+        behaviorNotes: "白天多活动,尤以早晨和下午活动较多,中午多在隐蔽处休息,晚上栖息于树冠隐蔽处",
+        distributionRange: "中国中部和西部山区",
+        similarSpecies: ["白腹锦鸡"],
+        isEndangered: false,
+        isExtinct: false,
+        featured: true
+    },
+    {
+        commonName: "丹顶鹤",
+        scientificName: "Grus japonensis",
+        description: "大型涉禽,体长120-160厘米。颈、脚较长,通体大多白色,头顶鲜红色,喉和颈黑色,耳至头枕白色,脚黑色,站立时颈、尾部飞羽和脚黑色,头顶红色,其余全为白色。",
+        conservationStatus: "濒危",
+        avgWeight: 7.5,
+        avgLength: 150,
+        wingspan: 220,
+        lifespan: 30,
+        diet: "杂食性,主要以鱼、虾、水生昆虫、软体动物、蝌蚪、沙蚕、蛤蜊、钉螺以及水生植物的茎、叶、块根、球茎和果实为食。",
+        habitat: "栖息于开阔平原、沼泽、湖泊、草地、海边滩涂、芦苇、沼泽以及河岸沼泽地带",
+        migrationPattern: "部分迁徙,部分留鸟",
+        breedingSeason: "4-6月",
+        clutchSize: 2,
+        incubationPeriod: 31,
+        vocalization: "高亢宏亮的鸣叫声",
+        identificationTips: "头顶红色,身体白色,颈部黑色",
+        behaviorNotes: "常成对或成家族群和小群活动。迁徙季节和冬季,常由数个或数十个家族群结成较大的群体。",
+        distributionRange: "东亚地区",
+        similarSpecies: ["白鹤", "灰鹤"],
+        isEndangered: true,
+        isExtinct: false,
+        featured: true
+    },
+    // 更多鸟类数据...
+];
+
+// 分类学测试数据
+const testTaxonomies = [
+    {
+        kingdom: "动物界",
+        phylum: "脊索动物门",
+        class: "鸟纲",
+        order: "鸡形目",
+        family: "雉科",
+        genus: "锦鸡属",
+        species: "红腹锦鸡",
+        scientificName: "Chrysolophus pictus",
+        description: "锦鸡属鸟类特征描述...",
+        characteristics: ["鲜艳羽毛", "性二态性明显", "陆禽"],
+        evolutionaryHistory: "起源于亚洲东部山区"
+    },
+    {
+        kingdom: "动物界",
+        phylum: "脊索动物门",
+        class: "鸟纲",
+        order: "鹤形目",
+        family: "鹤科",
+        genus: "鹤属",
+        species: "丹顶鹤",
+        scientificName: "Grus japonensis",
+        description: "鹤属鸟类特征描述...",
+        characteristics: ["长腿", "长颈", "大型涉禽"],
+        evolutionaryHistory: "古老物种,化石记录可追溯到中新世"
+    },
+    // 更多分类数据...
+];
+
+// 专业知识测试数据
+const testExpertise = [
+    {
+        title: "如何观察红腹锦鸡",
+        content: "观察红腹锦鸡的最佳时间是清晨和傍晚...详细观察技巧...",
+        author: "张观鸟",
+        experienceLevel: "中级",
+        applicationScenarios: ["野外考察", "生态摄影", "生物多样性调查"],
+        bestPractices: "穿着迷彩服,保持安静,使用望远镜远距离观察",
+        equipmentRecommendations: ["8-10倍双筒望远镜", "400mm以上长焦镜头", "迷彩伪装"],
+        seasonalityTips: "春季繁殖期雄鸟羽毛最艳丽,行为活跃",
+        locationTips: "四川、甘肃、陕西等地的山区阔叶林",
+        isVerified: true,
+        references: ["《中国鸟类野外手册》", "《中国雉类》"]
+    },
+    // 更多专业知识数据...
+];
+
+
+// 导入鸟类数据
+async function importBirds(birds: any[]) {
+    const results = [];
+    for (const birdData of birds) {
+        const bird = new CloudObject('Bird');
+        bird.set(birdData);
+        await bird.save();
+        results.push(bird);
+        console.log(`导入鸟类: ${bird.get('commonName')} (ID: ${bird.id})`);
+    }
+    return results;
+}
+
+// 导入分类数据
+async function importTaxonomies(taxonomies: any[]) {
+    const results = [];
+    for (const taxData of taxonomies) {
+        const tax = new CloudObject('Taxonomy');
+        tax.set(taxData);
+        await tax.save();
+        results.push(tax);
+        console.log(`导入分类: ${tax.get('species')} (ID: ${tax.id})`);
+    }
+    return results;
+}
+
+// 导入专业知识数据
+async function importExpertise(expertiseList: any[], relatedBirds: any[]) {
+    const results = [];
+    for (const expData of expertiseList) {
+        const exp = new CloudObject('BirdExpertise');
+
+        // 关联鸟类
+        if (expData.relatedBirds) {
+            expData.relatedBirds = expData.relatedBirds.map((birdName: string) => {
+                const bird = relatedBirds.find(b => b.get('commonName') === birdName);
+                return bird ? bird.toPointer() : null;
+            }).filter((b: any) => b !== null);
+        }
+
+        exp.set(expData);
+        await exp.save();
+        results.push(exp);
+        console.log(`导入专业知识: ${exp.get('title')} (ID: ${exp.id})`);
+    }
+    return results;
+}
+
+// 完整导入流程
+export async function importAllData() {
+    try {
+        console.log('开始导入分类数据...');
+        const taxonomies = await importTaxonomies(testTaxonomies);
+
+        console.log('\n开始导入鸟类数据...');
+        const birds = await importBirds(testBirds);
+
+        // 建立鸟类和分类的关联
+        for (const bird of birds) {
+            const scientificName = bird.get('scientificName');
+            const taxonomy = taxonomies.find(t => t.get('scientificName') === scientificName);
+            if (taxonomy) {
+                bird.set({ taxonomy: taxonomy.toPointer() });
+                await bird.save();
+                console.log(`关联 ${bird.get('commonName')} 与分类 ${taxonomy.get('species')}`);
+            }
+        }
+
+        console.log('\n开始导入专业知识数据...');
+        await importExpertise(testExpertise, birds);
+
+        console.log('\n所有数据导入完成!');
+    } catch (error) {
+        console.error('导入过程中出错:', error);
+    }
+}

+ 145 - 0
src/modules/demo/bird/page-bird-display/page-bird-display.component.html

@@ -0,0 +1,145 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-button text="返回" (click)="back()">返回</ion-button>
+    </ion-buttons>
+    <ion-title>{{bird?.get("commonName") || '鸟类详情'}}</ion-title>
+  </ion-toolbar>
+</ion-header>
+<ion-content class="ion-padding">
+  <div *ngIf="bird" class="bird-detail-container">
+    <!-- 头部卡片 -->
+    <ion-card class="header-card">
+      <ion-card-header>
+        <ion-card-title class="ion-text-center">
+          {{bird.get("commonName")}}
+          <small class="scientific-name">{{bird.get("scientificName")}}</small>
+        </ion-card-title>
+      </ion-card-header>
+
+      <ion-card-content>
+        <div class="conservation-status" [class.endangered]="bird.get('isEndangered')">
+          {{bird.get("conservationStatus")}}
+        </div>
+        <p class="description">{{bird.get("description")}}</p>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 基本信息网格 -->
+    <ion-grid class="info-grid">
+      <ion-row>
+        <ion-col size="6">
+          <ion-item>
+            <ion-label>平均体重</ion-label>
+            <ion-note slot="end">{{bird.get("avgWeight")}} kg</ion-note>
+          </ion-item>
+        </ion-col>
+        <ion-col size="6">
+          <ion-item>
+            <ion-label>平均体长</ion-label>
+            <ion-note slot="end">{{bird.get("avgLength")}} cm</ion-note>
+          </ion-item>
+        </ion-col>
+      </ion-row>
+      <ion-row>
+        <ion-col size="6">
+          <ion-item>
+            <ion-label>翼展</ion-label>
+            <ion-note slot="end">{{bird.get("wingspan")}} cm</ion-note>
+          </ion-item>
+        </ion-col>
+        <ion-col size="6">
+          <ion-item>
+            <ion-label>寿命</ion-label>
+            <ion-note slot="end">{{bird.get("lifespan")}} 年</ion-note>
+          </ion-item>
+        </ion-col>
+      </ion-row>
+    </ion-grid>
+
+    <!-- 栖息地与分布 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>栖息地与分布</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <p><strong>栖息地:</strong> {{bird.get("habitat")}}</p>
+        <p><strong>分布范围:</strong> {{bird.get("distributionRange")}}</p>
+        <p><strong>迁徙模式:</strong> {{bird.get("migrationPattern")}}</p>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 饮食习性 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>饮食习性</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <p>{{bird.get("diet")}}</p>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 繁殖信息 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>繁殖信息</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <ion-grid>
+          <ion-row>
+            <ion-col size="6">
+              <ion-item>
+                <ion-label>繁殖季节</ion-label>
+                <ion-note slot="end">{{bird.get("breedingSeason")}}</ion-note>
+              </ion-item>
+            </ion-col>
+            <ion-col size="6">
+              <ion-item>
+                <ion-label>每窝卵数</ion-label>
+                <ion-note slot="end">{{bird.get("clutchSize")}}</ion-note>
+              </ion-item>
+            </ion-col>
+          </ion-row>
+          <ion-row>
+            <ion-col>
+              <ion-item>
+                <ion-label>孵化期</ion-label>
+                <ion-note slot="end">{{bird.get("incubationPeriod")}} 天</ion-note>
+              </ion-item>
+            </ion-col>
+          </ion-row>
+        </ion-grid>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 行为特征 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>行为特征</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <p><strong>叫声:</strong> {{bird.get("vocalization")}}</p>
+        <p><strong>行为:</strong> {{bird.get("behaviorNotes")}}</p>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 识别特征 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>识别特征</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <p>{{bird.get("identificationTips")}}</p>
+        <p *ngIf="bird.get('similarSpecies')?.length > 0">
+          <strong>相似物种:</strong> {{bird.get("similarSpecies").join('、')}}
+        </p>
+      </ion-card-content>
+    </ion-card>
+  </div>
+
+  <!-- 加载状态 -->
+  <div *ngIf="!bird" class="loading-container">
+    <ion-spinner name="crescent"></ion-spinner>
+    <p>加载中...</p>
+  </div>
+</ion-content>

+ 89 - 0
src/modules/demo/bird/page-bird-display/page-bird-display.component.scss

@@ -0,0 +1,89 @@
+.bird-detail-container {
+    max-width: 800px;
+    margin: 0 auto;
+}
+
+.header-card {
+    margin-bottom: 20px;
+    text-align: center;
+
+    .scientific-name {
+        display: block;
+        font-style: italic;
+        font-size: 0.8em;
+        color: var(--ion-color-medium);
+    }
+}
+
+.conservation-status {
+    display: inline-block;
+    padding: 4px 8px;
+    border-radius: 12px;
+    background-color: var(--ion-color-success);
+    color: white;
+    font-size: 0.8em;
+    margin-bottom: 12px;
+
+    &.endangered {
+        background-color: var(--ion-color-danger);
+    }
+}
+
+.description {
+    text-align: justify;
+    line-height: 1.6;
+}
+
+.info-grid {
+    background: var(--ion-color-light);
+    border-radius: 12px;
+    margin-bottom: 20px;
+
+    ion-item {
+        --inner-padding-end: 0;
+        --padding-start: 0;
+    }
+}
+
+.loading-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 50vh;
+
+    ion-spinner {
+        width: 48px;
+        height: 48px;
+    }
+
+    p {
+        margin-top: 16px;
+        color: var(--ion-color-medium);
+    }
+}
+
+ion-card {
+    margin-bottom: 20px;
+    border-radius: 12px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+
+    ion-card-header {
+        padding-bottom: 0;
+
+        ion-card-title {
+            font-size: 1.2em;
+        }
+    }
+
+    ion-card-content {
+        p {
+            margin-bottom: 12px;
+            line-height: 1.6;
+
+            &:last-child {
+                margin-bottom: 0;
+            }
+        }
+    }
+}

+ 22 - 0
src/modules/demo/bird/page-bird-display/page-bird-display.component.spec.ts

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

+ 36 - 0
src/modules/demo/bird/page-bird-display/page-bird-display.component.ts

@@ -0,0 +1,36 @@
+import { CommonModule } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { IonicModule, NavController } from '@ionic/angular';
+
+@Component({
+  selector: 'app-page-bird-display',
+  templateUrl: './page-bird-display.component.html',
+  styleUrls: ['./page-bird-display.component.scss'],
+  imports: [CommonModule, IonicModule],
+  standalone: true,
+})
+export class PageBirdDisplayComponent implements OnInit {
+  bird: CloudObject | undefined | null;
+
+  back() {
+    this.navCtrl.back()
+  }
+  constructor(
+    private route: ActivatedRoute,
+    private navCtrl: NavController
+  ) {
+    this.route.params.subscribe(params => {
+      console.log(params);
+      this.loadBird(params['birdId']);
+    });
+  }
+
+  async loadBird(birdId: string) {
+    let query = new CloudQuery("Bird");
+    this.bird = await query.get(birdId);
+  }
+
+  ngOnInit() { }
+}

+ 70 - 0
src/modules/demo/bird/page-bird-list/page-bird-list.component.html

@@ -0,0 +1,70 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-title>鸟类数据库</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="importData()">
+        <ion-icon slot="icon-only" name="cloud-download"></ion-icon>
+      </ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <!-- 鸟类列表 -->
+  <ion-list>
+    <ion-list-header>
+      <ion-label>鸟类名录</ion-label>
+    </ion-list-header>
+
+    <ion-item-sliding *ngFor="let bird of birdList">
+      <ion-item (click)="goBird(bird)" detail>
+        <ion-label>
+          <h2>{{bird?.get('commonName')}}</h2>
+          <p>{{bird?.get('scientificName')}}</p>
+        </ion-label>
+      </ion-item>
+
+      <ion-item-options side="end">
+        <ion-item-option color="primary" (click)="showTaxonomy(bird)">
+          <ion-icon slot="icon-only" name="podium"></ion-icon>
+        </ion-item-option>
+      </ion-item-options>
+    </ion-item-sliding>
+  </ion-list>
+
+  <!-- 分类学信息 -->
+  <ion-card *ngIf="selectedTaxonomy">
+    <ion-card-header>
+      <ion-card-title>分类信息</ion-card-title>
+      <ion-card-subtitle>{{selectedTaxonomy?.get('species')}}</ion-card-subtitle>
+    </ion-card-header>
+
+    <ion-card-content>
+      <ion-grid>
+        <ion-row>
+          <ion-col size="6"><strong>界:</strong> {{selectedTaxonomy?.get('kingdom')}}</ion-col>
+          <ion-col size="6"><strong>门:</strong> {{selectedTaxonomy?.get('phylum')}}</ion-col>
+          <ion-col size="6"><strong>纲:</strong> {{selectedTaxonomy?.get('class')}}</ion-col>
+          <ion-col size="6"><strong>目:</strong> {{selectedTaxonomy?.get('order')}}</ion-col>
+          <ion-col size="6"><strong>科:</strong> {{selectedTaxonomy?.get('family')}}</ion-col>
+          <ion-col size="6"><strong>属:</strong> {{selectedTaxonomy?.get('genus')}}</ion-col>
+        </ion-row>
+      </ion-grid>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 专业知识 -->
+  <ion-list>
+    <ion-list-header>
+      <ion-label>观鸟专业知识</ion-label>
+    </ion-list-header>
+
+    <ion-item *ngFor="let exp of expList" detail (click)="showExpertise(exp)">
+      <ion-label>
+        <h2>{{exp?.get('title')}}</h2>
+        <p>{{exp?.get('author')}} • {{exp?.get('experienceLevel')}}</p>
+      </ion-label>
+      <ion-badge slot="end" *ngIf="exp?.get('isVerified')" color="success">已验证</ion-badge>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 42 - 0
src/modules/demo/bird/page-bird-list/page-bird-list.component.scss

@@ -0,0 +1,42 @@
+ion-content {
+    --background: #f5f5f5;
+}
+
+ion-card {
+    margin: 16px;
+    border-radius: 12px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+ion-item {
+    --border-radius: 8px;
+    margin-bottom: 8px;
+    --padding-start: 12px;
+    --padding-end: 12px;
+}
+
+ion-list {
+    background: transparent;
+}
+
+ion-list-header {
+    font-weight: bold;
+    font-size: 1.1rem;
+}
+
+.scientific-name {
+    color: var(--ion-color-medium);
+    font-style: italic;
+}
+
+ion-badge {
+    margin-left: 8px;
+}
+
+ion-grid {
+    padding: 0;
+}
+
+ion-col {
+    padding: 4px 0;
+}

+ 22 - 0
src/modules/demo/bird/page-bird-list/page-bird-list.component.spec.ts

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

+ 68 - 0
src/modules/demo/bird/page-bird-list/page-bird-list.component.ts

@@ -0,0 +1,68 @@
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { NavController } from "@ionic/angular/standalone";
+import { IonHeader, IonButtons, IonToolbar, IonTitle, IonContent, IonList, IonItem, IonLabel, IonButton, IonIcon, IonBadge, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonGrid, IonRow, IonCol, IonListHeader, IonItemSliding, IonItemOptions, IonItemOption } from "@ionic/angular/standalone";
+import { addIcons } from "ionicons";
+import { cloudDownload, podium } from "ionicons/icons";
+import { Component, OnInit } from '@angular/core';
+import { importAllData } from '../import-bird-data';
+import { CommonModule } from '@angular/common';
+
+
+@Component({
+  selector: 'app-page-bird-list',
+  templateUrl: './page-bird-list.component.html',
+  styleUrls: ['./page-bird-list.component.scss'],
+  standalone: true,
+  imports: [
+    CommonModule,
+    IonHeader, IonButtons, IonToolbar, IonTitle,
+    IonContent, IonList, IonItem, IonLabel, IonButton, IonIcon,
+    IonBadge, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonGrid, IonRow, IonCol, IonListHeader, IonItemSliding, IonItemOptions, IonItemOption]
+})
+export class PageBirdListComponent implements OnInit {
+  birdList: Array<CloudObject> = [];
+  expList: Array<CloudObject> = [];
+  taxList: Array<CloudObject> = [];
+  selectedTaxonomy: CloudObject | null = null;
+  selectedExpertise: CloudObject | null = null;
+
+  constructor(
+    private navCtrl: NavController
+  ) {
+    addIcons({ cloudDownload, podium });
+  }
+
+  ngOnInit() {
+    this.loadBirdData();
+  }
+
+  async loadBirdData() {
+    let query1 = new CloudQuery("Bird");
+    this.birdList = await query1.find();
+
+    let query2 = new CloudQuery("BirdExpertise");
+    this.expList = await query2.find();
+
+    let query3 = new CloudQuery("Taxonomy");
+    this.taxList = await query3.find();
+  }
+
+  goBird(bird: CloudObject) {
+    this.navCtrl.navigateForward(['tabs', 'demo', 'bird', 'display', bird?.id]);
+  }
+
+  showTaxonomy(bird: CloudObject) {
+    const species = bird.get('species');
+    this.selectedTaxonomy = this.taxList.find(tax => tax.get('species') === species) || null;
+  }
+
+  showExpertise(exp: CloudObject) {
+    this.selectedExpertise = exp;
+    // 这里可以添加显示专业知识的逻辑,比如打开模态框
+  }
+
+  importData() {
+    importAllData();
+  }
+
+}