ソースを参照

feat:book-list/display

Midnight 3 週間 前
コミット
e2d0c9e674

+ 12 - 5
myapp/src/app/tabs/tabs-routing.module.ts

@@ -15,10 +15,6 @@ const routes: Routes = [
         path: 'tab2',
         loadChildren: () => import('../tab2/tab2.module').then(m => m.Tab2PageModule)
       },
-      {
-        path: 'tab3',
-        loadChildren: () => import('../tab3/tab3.module').then(m => m.Tab3PageModule)
-      },
       {
         path: 'mine',
         loadComponent: () =>
@@ -28,7 +24,18 @@ const routes: Routes = [
         path: '',
         redirectTo: '/tabs/tab1',
         pathMatch: 'full'
-      }
+      },
+      {
+        path: 'demo/book/list',       
+        loadComponent: () =>
+          import('../../modules/demo/book/page-book-list/page-book-list.component').then((m) => m.PageBookListComponent),
+      },
+      {
+        path: 'demo/book/display/:bookId',       
+        loadComponent: () =>
+          import('../../modules/demo/book/page-book-display/page-book-display.component').then((m) => m.PageBookDisplayComponent),
+      },
+      
     ]
   },
   {

+ 1 - 1
myapp/src/app/tabs/tabs.page.html

@@ -11,7 +11,7 @@
       <ion-label>咨询</ion-label>
     </ion-tab-button>
 
-    <ion-tab-button tab="tab3" href="/tabs/tab3">
+    <ion-tab-button tab="demo/book/list" href="/tabs/demo/book/list">
       <ion-icon aria-hidden="true" name="book-outline"></ion-icon>
       <ion-label>文书</ion-label>
     </ion-tab-button>

+ 4 - 2
myapp/src/lib/user/page-mine/page-mine.component.ts

@@ -8,9 +8,9 @@ import { IonAvatar,
   IonItem, 
   IonLabel, 
   IonBadge ,IonContent, AlertController,ModalController } from '@ionic/angular/standalone';
-import { CloudUser } from 'src/lib/ncloud';
 import { ModalUserEditComponent } from '../modal-user-edit/modal-user-edit.component';
 import { FormsModule } from '@angular/forms';
+import { CloudUser } from 'src/lib/ncloud';
 
 @Component({
   selector: 'app-page-mine',
@@ -34,7 +34,9 @@ import { FormsModule } from '@angular/forms';
 export class PageMineComponent  implements OnInit {
   currentUser: CloudUser | undefined;
 
-  constructor(private modalCtrl: ModalController) {
+  constructor(
+    private modalCtrl: ModalController
+  ) {
     this.currentUser = new CloudUser();
   }
 

+ 137 - 0
myapp/src/modules/demo/book/README.md

@@ -0,0 +1,137 @@
+{
+        path: 'dome/book/list',       
+        loadComponent: () =>
+          import('../../modules/demo/book/page-book-list/page-book-list.component').then((m) => m.PageBookListComponent),
+      },
+      {
+        path: 'dome/book/display/:bookId',       
+        loadComponent: () =>
+          import('../../modules/demo/book/page-book-display/page-book-display.component').then((m) => m.PageBookDisplayComponent),
+      },
+
+
+- 思考法律文书知识科普的列表页面,以及法律文书知识的范式数据
+    
+
+设定要求 您是一名专业的数据库工程师,熟悉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 LegalKnowledgeDatabase
+
+class LegalDocument {
+  +String objectId
+  +Date createdAt
+  +Date updatedAt
+  
+  +String title
+  +String docType
+  +String content
+  +String description
+  +String applicableScope
+  +String legalBasis
+  +String keywords
+  +Number version
+  +Date effectiveDate
+  +Date expiryDate
+  +Bool isActive
+  +Number importance
+  +Array relatedLaws
+  +Array sampleCases
+  +File templateFile
+  +Pointer category
+  +String notes
+}
+
+class DocumentCategory {
+  +String objectId
+  +Date createdAt
+  +Date updatedAt
+  
+  +String name
+  +String description
+  +Number displayOrder
+  +String icon
+  +Bool isFeatured
+  +Array tags
+  +Pointer parentCategory
+}
+
+class LegalKnowledge {
+  +String objectId
+  +Date createdAt
+  +Date updatedAt
+  
+  +String title
+  +String content
+  +String summary
+  +Array applicableScenarios
+  +Array relatedDocuments
+  +Number popularity
+  +String source
+  +Date publishDate
+  +String author
+  +Array keywords
+  +Pointer category
+  +Bool isVerified
+}
+
+LegalDocument "1" *-- "1" DocumentCategory : category
+LegalKnowledge "1" *-- "1" DocumentCategory : category
+DocumentCategory "1" *-- "0..1" DocumentCategory : parentCategory
+
+@enduml
+表结构说明:
+LegalDocument(法律文书表):
+
+核心字段:title(标题)、docType(文书类型)、content(内容)、description(整体描述)
+
+丰富参数:applicableScope(适用范围)、legalBasis(法律依据)、keywords(关键词)
+
+实用字段:version(版本)、effectiveDate(生效日期)、expiryDate(失效日期)
+
+关联字段:category(分类指针)、relatedLaws(相关法律数组)、sampleCases(案例数组)
+
+文件字段:templateFile(模板文件)
+
+管理字段:isActive(是否有效)、importance(重要程度)
+
+DocumentCategory(法律知识分类表):
+
+核心字段:name(分类名称)、description(分类描述)
+
+结构字段:parentCategory(父分类指针,支持层级结构)
+
+展示字段:displayOrder(显示顺序)、icon(图标)、isFeatured(是否推荐)
+
+标记字段:tags(标签数组)
+
+LegalKnowledge(法律专业知识表):
+
+核心字段:title(标题)、content(内容)、summary(摘要)
+
+应用字段:applicableScenarios(适用场景数组)、relatedDocuments(相关文书数组)
+
+质量字段:isVerified(是否审核)、source(来源)、author(作者)
+
+热度字段:popularity(受欢迎程度)、publishDate(发布日期)
+
+关系说明:
+LegalDocument和LegalKnowledge都通过Pointer关联到DocumentCategory
+
+DocumentCategory支持自关联,形成层级分类结构
+
+使用Array类型存储多值字段(如关键词、相关法律等)
+
+使用File类型存储文书模板文件

+ 250 - 0
myapp/src/modules/demo/book/import-book-data.ts

@@ -0,0 +1,250 @@
+import { CloudObject } from "src/lib/ncloud";
+
+// 法律文书分类测试数据
+const documentCategories = [
+  {
+    name: "民事诉讼文书",
+    description: "与民事纠纷相关的法律文书",
+    displayOrder: 1,
+    icon: "civil-icon.png",
+    isFeatured: true,
+    tags: ["民事", "诉讼", "纠纷"]
+  },
+  {
+    name: "刑事诉讼文书",
+    description: "与刑事案件相关的法律文书",
+    displayOrder: 2,
+    icon: "criminal-icon.png",
+    isFeatured: true,
+    tags: ["刑事", "犯罪", "公诉"]
+  },
+  {
+    name: "行政诉讼文书",
+    description: "与行政争议相关的法律文书",
+    displayOrder: 3,
+    icon: "admin-icon.png",
+    isFeatured: false,
+    tags: ["行政", "政府", "复议"]
+  },
+  {
+    name: "合同协议文书",
+    description: "各类合同协议范本",
+    displayOrder: 4,
+    icon: "contract-icon.png",
+    isFeatured: true,
+    tags: ["合同", "协议", "商业"]
+  }
+];
+
+// 法律文书测试数据
+const legalDocuments = [
+  {
+    title: "民事起诉状范本",
+    docType: "起诉状",
+    content: "原告:XXX,性别,出生日期,民族,职业,住址...",
+    description: "标准民事起诉状模板,适用于一般民事纠纷",
+    applicableScope: "合同纠纷、侵权纠纷等民事案件",
+    legalBasis: "《中华人民共和国民事诉讼法》第一百二十条",
+    keywords: ["民事", "起诉状", "模板"],
+    version: 1.2,
+    effectiveDate: new Date("2023-01-01"),
+    expiryDate: new Date("2025-12-31"),
+    isActive: true,
+    importance: 5,
+    relatedLaws: ["民事诉讼法", "民法典"],
+    sampleCases: ["(2023)京01民初123号"],
+    notes: "使用时需根据具体案情修改"
+  },
+  {
+    title: "刑事辩护词",
+    docType: "辩护词",
+    content: "辩护人依法接受委托,现就本案发表如下辩护意见...",
+    description: "刑事案件辩护律师使用的标准辩护词",
+    applicableScope: "一审刑事案件",
+    legalBasis: "《中华人民共和国刑事诉讼法》第三十五条",
+    keywords: ["刑事", "辩护", "律师"],
+    version: 1.1,
+    effectiveDate: new Date("2023-03-15"),
+    isActive: true,
+    importance: 4,
+    relatedLaws: ["刑事诉讼法", "刑法"],
+    sampleCases: ["(2023)沪刑初456号"],
+    notes: "需结合案件证据材料具体撰写"
+  },
+  {
+    title: "劳动合同范本",
+    docType: "合同",
+    content: "甲方(用人单位):XXX公司,地址:...",
+    description: "标准劳动合同模板",
+    applicableScope: "企业与员工签订劳动合同",
+    legalBasis: "《中华人民共和国劳动合同法》第十七条",
+    keywords: ["劳动", "合同", "用工"],
+    version: 2.0,
+    effectiveDate: new Date("2022-06-01"),
+    isActive: true,
+    importance: 5,
+    relatedLaws: ["劳动合同法", "劳动法"],
+    sampleCases: ["参考人社局发布的标准文本"],
+    notes: "可根据企业实际情况调整补充条款"
+  }
+];
+
+// 法律知识测试数据
+const legalKnowledge = [
+  {
+    title: "如何正确撰写民事起诉状",
+    content: "撰写民事起诉状需要注意以下要点...",
+    summary: "民事起诉状撰写指南",
+    applicableScenarios: ["准备起诉", "法律咨询", "自我维权"],
+    popularity: 8,
+    source: "最高人民法院司法案例研究院",
+    publishDate: new Date("2023-05-10"),
+    author: "张律师",
+    keywords: ["起诉状", "撰写", "指南"],
+    isVerified: true
+  },
+  {
+    title: "刑事案件辩护策略",
+    content: "刑事辩护中常用的策略包括...",
+    summary: "刑事辩护方法与技巧",
+    applicableScenarios: ["刑事辩护", "律师培训"],
+    popularity: 7,
+    source: "中国律师协会",
+    publishDate: new Date("2023-02-20"),
+    author: "李律师",
+    keywords: ["刑事", "辩护", "策略"],
+    isVerified: true
+  },
+  {
+    title: "劳动合同签订注意事项",
+    content: "签订劳动合同时需注意以下事项...",
+    summary: "劳动合同签订指南",
+    applicableScenarios: ["入职签订", "HR工作", "劳动维权"],
+    popularity: 9,
+    source: "人力资源和社会保障部",
+    publishDate: new Date("2023-04-15"),
+    author: "王顾问",
+    keywords: ["劳动", "合同", "签订"],
+    isVerified: true
+  }
+];
+
+
+
+
+// 导入函数实现
+export class DataImporter {
+  /**
+   * 批量导入分类数据
+   */
+  static async importCategories() {
+    const results = [];
+    for (const categoryData of documentCategories) {
+      const category = new CloudObject("DocumentCategory");
+      category.set(categoryData);
+      await category.save();
+      results.push(category);
+      console.log(`导入分类: ${categoryData.name}`, category.id);
+    }
+    return results;
+  }
+
+  /**
+   * 批量导入法律文书数据
+   * @param categoryMap 分类映射表 {分类名称: 分类objectId}
+   */
+  static async importLegalDocuments(categoryMap: Record<string, string>) {
+    const results = [];
+    for (const docData of legalDocuments) {
+      const doc = new CloudObject("LegalDocument");
+      
+      // 设置分类指针
+      if (docData.docType) {
+        let categoryName = "";
+        if (docData.docType.includes("民事")) categoryName = "民事诉讼文书";
+        else if (docData.docType.includes("刑事")) categoryName = "刑事诉讼文书";
+        else if (docData.docType.includes("合同")) categoryName = "合同协议文书";
+        
+        if (categoryName && categoryMap[categoryName]) {
+          doc.set({
+            ...docData,
+            category: {
+              __type: "Pointer",
+              className: "DocumentCategory",
+              objectId: categoryMap[categoryName]
+            }
+          });
+        }
+      }
+      
+      await doc.save();
+      results.push(doc);
+      console.log(`导入文书: ${docData.title}`, doc.id);
+    }
+    return results;
+  }
+
+  /**
+   * 批量导入法律知识数据
+   * @param categoryMap 分类映射表 {分类名称: 分类objectId}
+   */
+  static async importLegalKnowledge(categoryMap: Record<string, string>) {
+    const results = [];
+    for (const knowledgeData of legalKnowledge) {
+      const knowledge = new CloudObject("LegalKnowledge");
+      
+      // 设置分类指针
+      let categoryName = "";
+      if (knowledgeData.title.includes("民事")) categoryName = "民事诉讼文书";
+      else if (knowledgeData.title.includes("刑事")) categoryName = "刑事诉讼文书";
+      else if (knowledgeData.title.includes("劳动")) categoryName = "合同协议文书";
+      
+      if (categoryName && categoryMap[categoryName]) {
+        knowledge.set({
+          ...knowledgeData,
+          category: {
+            __type: "Pointer",
+            className: "DocumentCategory",
+            objectId: categoryMap[categoryName]
+          }
+        });
+      }
+      
+      await knowledge.save();
+      results.push(knowledge);
+      console.log(`导入知识: ${knowledgeData.title}`, knowledge.id);
+    }
+    return results;
+  }
+
+  /**
+   * 执行完整导入流程
+   */
+  static async executeFullImport() {
+    try {
+      // 1. 先导入分类
+      const categories = await this.importCategories();
+      const categoryMap: Record<string, string> = {};
+      categories.forEach(cat => {
+        const name = cat.get("name");
+        if (name && cat.id) {
+          categoryMap[name] = cat.id;
+        }
+      });
+
+      // 2. 导入文书和知识(带分类关联)
+      await this.importLegalDocuments(categoryMap);
+      await this.importLegalKnowledge(categoryMap);
+
+      console.log("所有数据导入完成");
+      return true;
+    } catch (error) {
+      console.error("数据导入失败:", error);
+      return false;
+    }
+  }
+}
+
+// 使用示例
+// DataImporter.executeFullImport();
+

+ 66 - 0
myapp/src/modules/demo/book/page-book-display/page-book-display.component.html

@@ -0,0 +1,66 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-buttons slot="start">
+      <ion-back-button (click)="back()"></ion-back-button>
+    </ion-buttons>
+    <ion-title>法律文书详情</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <div *ngIf="book" class="document-detail-container">
+    <!-- 头部卡片 -->
+    <ion-card class="document-header-card">
+      <ion-card-header>
+        <div class="icon-container">
+          <ion-img [src]="book?.get('icon')" alt="文书类型图标" class="document-icon"></ion-img>
+        </div>
+        <ion-card-title class="document-title">{{book?.get('name')}}</ion-card-title>
+        <ion-card-subtitle *ngIf="book?.get('isFeatured')" class="featured-badge">
+          <ion-chip color="warning">
+            <ion-icon name="star"></ion-icon>
+            <ion-label>精选</ion-label>
+          </ion-chip>
+        </ion-card-subtitle>
+      </ion-card-header>
+    </ion-card>
+
+    <!-- 描述部分 -->
+    <ion-card class="description-card">
+      <ion-card-content>
+        <h2 class="section-title">文书描述</h2>
+        <p class="document-description">{{book?.get('description')}}</p>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 标签部分 -->
+    <ion-card class="tags-card" *ngIf="book?.get('tags')?.length > 0">
+      <ion-card-content>
+        <h2 class="section-title">相关标签</h2>
+        <div class="tags-container">
+          <ion-chip *ngFor="let tag of book?.get('tags')" color="medium" outline>
+            {{tag}}
+          </ion-chip>
+        </div>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 操作按钮 -->
+    <div class="action-buttons">
+      <ion-button expand="block" color="primary" shape="round">
+        <ion-icon slot="start" name="download"></ion-icon>
+        下载模板
+      </ion-button>
+      <ion-button expand="block" color="secondary" fill="outline" shape="round">
+        <ion-icon slot="start" name="create"></ion-icon>
+        在线编辑
+      </ion-button>
+    </div>
+  </div>
+
+  <!-- 加载状态 -->
+  <div *ngIf="!book" class="loading-container">
+    <ion-spinner name="crescent"></ion-spinner>
+    <p>正在加载文书详情...</p>
+  </div>
+</ion-content>

+ 87 - 0
myapp/src/modules/demo/book/page-book-display/page-book-display.component.scss

@@ -0,0 +1,87 @@
+.document-detail-container {
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.document-header-card {
+  text-align: center;
+  box-shadow: 0 4px 16px rgba(var(--ion-color-primary-rgb), 0.1);
+  margin-bottom: 24px;
+
+  .icon-container {
+    display: flex;
+    justify-content: center;
+    margin-bottom: 16px;
+  }
+
+  .document-icon {
+    width: 80px;
+    height: 80px;
+    object-fit: contain;
+  }
+
+  .document-title {
+    font-size: 1.5rem;
+    font-weight: bold;
+    color: var(--ion-color-primary);
+    margin-bottom: 8px;
+  }
+
+  .featured-badge {
+    margin-top: 8px;
+  }
+}
+
+.description-card, .tags-card {
+  margin-bottom: 20px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+
+  .section-title {
+    font-size: 1.2rem;
+    color: var(--ion-color-primary);
+    margin-bottom: 12px;
+  }
+
+  .document-description {
+    line-height: 1.6;
+    color: var(--ion-color-medium);
+  }
+}
+
+.tags-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.action-buttons {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: 24px;
+
+  ion-button {
+    --border-radius: 50px;
+    height: 48px;
+    font-weight: 500;
+  }
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 200px;
+
+  ion-spinner {
+    width: 48px;
+    height: 48px;
+  }
+
+  p {
+    margin-top: 16px;
+    color: var(--ion-color-medium);
+  }
+}

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

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

+ 81 - 0
myapp/src/modules/demo/book/page-book-display/page-book-display.component.ts

@@ -0,0 +1,81 @@
+import { CommonModule } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { 
+  IonHeader, 
+  IonToolbar, 
+  IonTitle, 
+  IonContent, 
+  IonButtons, 
+  IonBackButton, 
+  IonCard, 
+  IonCardHeader, 
+  IonCardTitle, 
+  IonCardSubtitle, 
+  IonCardContent, 
+  IonChip, 
+  IonLabel, 
+  IonIcon, 
+  IonButton, 
+  IonImg, 
+  IonSpinner, 
+  NavController
+} from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { star, download, create } from 'ionicons/icons';
+
+@Component({
+  selector: 'app-page-book-display',
+  templateUrl: './page-book-display.component.html',
+  styleUrls: ['./page-book-display.component.scss'],
+  standalone: true,
+  imports: [
+    CommonModule,
+    IonHeader, 
+    IonToolbar, 
+    IonTitle, 
+    IonContent, 
+    IonButtons, 
+    IonBackButton, 
+    IonCard, 
+    IonCardHeader, 
+    IonCardTitle, 
+    IonCardSubtitle, 
+    IonCardContent, 
+    IonChip, 
+    IonLabel, 
+    IonIcon, 
+    IonButton, 
+    IonImg, 
+    IonSpinner
+  ]
+})
+export class PageBookDisplayComponent  implements OnInit {
+
+   book: CloudObject | undefined | null;
+
+  back(){
+    this.navCtrl.back()
+  }
+
+  constructor(
+    private route: ActivatedRoute,
+    private navCtrl: NavController
+  ) {
+    addIcons({ star, download, create });
+    
+    this.route.params.subscribe(params => {
+      console.log(params);
+      this.loadBook(params['bookId']);
+    });
+  }
+
+  async loadBook(bookId: string) {
+    let query = new CloudQuery("DocumentCategory");
+    this.book = await query.get(bookId);
+  }
+
+  ngOnInit() {}
+
+}

+ 81 - 0
myapp/src/modules/demo/book/page-book-list/page-book-list.component.html

@@ -0,0 +1,81 @@
+  
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-title>法律文书库</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <!-- 分类卡片 -->
+  <ion-list>
+    <ion-list-header>
+      <ion-label>文书分类</ion-label>
+    </ion-list-header>
+    
+    <ion-grid>
+      <ion-row>
+        @for(documentCategory of categorieList; track documentCategory){
+          <ion-col size="6" size-md="4" size-lg="3">
+            <ion-card (click)="goBook(documentCategory)" class="category-card">
+              <ion-icon name="document-text-outline" class="category-icon"></ion-icon>
+              <ion-card-header>
+                <ion-card-title>{{documentCategory.get('name')}}</ion-card-title>
+              </ion-card-header>
+              <ion-card-content>
+                {{documentCategory.get('description') || '暂无描述'}}
+              </ion-card-content>
+            </ion-card>
+          </ion-col>
+        }
+      </ion-row>
+    </ion-grid>
+  </ion-list>
+
+  <!-- 精选法律文书 -->
+  <ion-list>
+    <ion-list-header>
+      <ion-label>精选法律文书</ion-label>
+    </ion-list-header>
+    
+    @for(legalDocument of documentList; track legalDocument){
+      <ion-item detail="true" lines="full">
+        <ion-icon name="document-outline" slot="start" color="primary"></ion-icon>
+        <ion-label>
+          <h3>{{legalDocument.get('title')}}</h3>
+          <p>{{legalDocument.get('description') || '暂无描述'}}</p>
+          <p class="meta-info">
+            <ion-badge color="light">{{legalDocument.get('docType')}}</ion-badge>
+            <span>适用: {{legalDocument.get('applicableScope')}}</span>
+          </p>
+        </ion-label>
+      </ion-item>
+    }
+  </ion-list>
+
+  <!-- 法律知识 -->
+  <ion-list>
+    <ion-list-header>
+      <ion-label>法律知识</ion-label>
+    </ion-list-header>
+    
+    @for(legalKnowledge of knowledgeList; track legalKnowledge){
+      <ion-item detail="true" lines="full">
+        <ion-icon name="book-outline" slot="start" color="secondary"></ion-icon>
+        <ion-label>
+          <h3>{{legalKnowledge.get('title')}}</h3>
+          <p>{{legalKnowledge.get('summary') || '暂无摘要'}}</p>
+          <p class="meta-info">
+            <ion-badge color="light">{{legalKnowledge.get('source')}}</ion-badge>
+            <span>{{legalKnowledge.get('publishDate') | date:'yyyy-MM-dd'}}</span>
+          </p>
+        </ion-label>
+      </ion-item>
+    }
+  </ion-list>
+
+  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
+    <ion-fab-button color="danger">
+      <ion-icon name="cloud-download-outline"></ion-icon>
+    </ion-fab-button>
+  </ion-fab>
+</ion-content>

+ 47 - 0
myapp/src/modules/demo/book/page-book-list/page-book-list.component.scss

@@ -0,0 +1,47 @@
+.category-card {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  transition: transform 0.3s ease;
+  
+  &:active {
+    transform: scale(0.98);
+  }
+}
+
+.category-icon {
+  font-size: 2.5rem;
+  margin: 16px auto;
+  color: var(--ion-color-primary);
+}
+
+ion-card-header {
+  padding-bottom: 0;
+}
+
+ion-card-content {
+  flex-grow: 1;
+  padding-top: 0;
+}
+
+.meta-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 8px;
+  font-size: 0.8rem;
+  color: var(--ion-color-medium);
+}
+
+ion-badge {
+  margin-right: 8px;
+}
+
+ion-list {
+  margin-bottom: 24px;
+}
+
+ion-list-header ion-label {
+  font-size: 1.2rem;
+  font-weight: bold;
+}

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

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

+ 57 - 0
myapp/src/modules/demo/book/page-book-list/page-book-list.component.ts

@@ -0,0 +1,57 @@
+import { Component, OnInit } from '@angular/core';
+import { DataImporter } from '../import-book-data';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { NavController } from '@ionic/angular/standalone';
+import { DatePipe } from '@angular/common';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonListHeader, IonLabel, IonItem, IonIcon, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonGrid, IonRow, IonCol, IonBadge, IonFab, IonFabButton } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { documentTextOutline, documentOutline, bookOutline, cloudDownloadOutline } from 'ionicons/icons';
+
+
+@Component({
+  selector: 'app-page-book-list',
+  templateUrl: './page-book-list.component.html',
+  styleUrls: ['./page-book-list.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonListHeader, IonLabel, IonItem, IonIcon, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonGrid, IonRow, IonCol, IonBadge, IonFab, IonFabButton, DatePipe]
+})
+export class PageBookListComponent  implements OnInit {
+
+  constructor(
+    private navCtrl: NavController
+  ) {
+    addIcons({ documentTextOutline, documentOutline, bookOutline, cloudDownloadOutline });
+  }
+
+  ngOnInit() {
+    this.loadBookData();
+  }
+
+  categorieList: Array<CloudObject> = [];
+  documentList: Array<CloudObject> = [];
+  knowledgeList: Array<CloudObject> = [];
+
+  async loadBookData() {
+    try {
+      let query1 = new CloudQuery("DocumentCategory");
+      this.categorieList = await query1.find();
+      
+      let query2 = new CloudQuery("LegalDocument");
+      this.documentList = await query2.find();
+      
+      let query3 = new CloudQuery("LegalKnowledge");
+      this.knowledgeList = await query3.find();
+    } catch (error) {
+      console.error('加载数据失败:', error);
+    }
+  }
+
+  goBook(book: CloudObject) {
+    this.navCtrl.navigateForward(['tabs', 'demo', 'book', 'display', book?.id]);
+  }
+
+  importData() {
+    DataImporter.executeFullImport();
+  }
+
+}