Pārlūkot izejas kodu

Merge branch 'master' of http://git.fmode.cn:3000/19808003398/20222670105

s202226701043 3 mēneši atpakaļ
vecāks
revīzija
bbd1538f48
27 mainītis faili ar 490 papildinājumiem un 255 dzēšanām
  1. 14 3
      novel-app/src/app/agent-create/agent-create.page.html
  2. 22 0
      novel-app/src/app/agent-create/agent-create.page.scss
  3. 13 5
      novel-app/src/app/agent-create/agent-create.page.ts
  4. 4 0
      novel-app/src/app/app.routes.ts
  5. 5 1
      novel-app/src/app/atest/atest.page.html
  6. 10 3
      novel-app/src/app/atest/atest.page.ts
  7. 6 9
      novel-app/src/app/chapter-generator/chapter-generator.page.html
  8. 5 1
      novel-app/src/app/chapter-generator/chapter-generator.page.scss
  9. 8 12
      novel-app/src/app/chapter-generator/chapter-generator.page.ts
  10. 3 0
      novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.html
  11. 0 0
      novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.scss
  12. 22 0
      novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.spec.ts
  13. 66 0
      novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.ts
  14. 3 7
      novel-app/src/app/component/article-card/article-card.component.html
  15. 17 8
      novel-app/src/app/component/article-card/article-card.component.ts
  16. 1 1
      novel-app/src/app/home/home.page.scss
  17. 166 0
      novel-app/src/app/hwobs.service.ts
  18. 2 2
      novel-app/src/app/short-generator/short-generator.page.html
  19. 14 16
      novel-app/src/app/tab2/tab2.page.html
  20. 26 142
      novel-app/src/app/tab2/tab2.page.scss
  21. 44 36
      novel-app/src/app/tab2/tab2.page.ts
  22. 3 1
      novel-app/src/app/tab4/tab4.page.html
  23. 24 6
      novel-app/src/app/tab4/tab4.page.scss
  24. 5 0
      novel-app/src/app/tabs/tabs.page.html
  25. 2 2
      novel-app/src/app/tabs/tabs.page.ts
  26. 5 0
      novel-app/src/app/tabs/tabs.routes.ts
  27. BIN
      novel-app/src/assets/images/background-image6.jpg

+ 14 - 3
novel-app/src/app/agent-create/agent-create.page.html

@@ -9,7 +9,9 @@
 </ion-header>
 
 
-<ion-content [fullscreen]="true">
+<ion-content [fullscreen]="true" class="ion-padding"
+  style="background-image: url('../../assets/images/background-image6.jpg'); background-size: cover; background-position: center; background-repeat: no-repeat;">
+
   <div style="text-align: center; margin-top: 20px">
     <img height="80px" [src]="currentUser.get('avatar')" alt="" style="border-radius: 50%;">
     <p>智能体创建</p>
@@ -37,7 +39,9 @@
             <p style="width:120px;font-weight: bolder;">设定描述:</p>
             <ion-textarea [value]="desc" placeholder="示例:" (ionInput)="descInput($event)" autoGrow="true"></ion-textarea> 
         </ion-item>
-        <ion-item>
+        
+        
+        <!-- <ion-item>
           <ion-label position="stacked">上传角色图片</ion-label>
           <div class="image-upload" (click)="fileInput.click()">
             <input type="file" #fileInput (change)="onFileSelected($event)" accept="image/*" style="display: none;" />
@@ -47,11 +51,18 @@
             <div class="upload-text" *ngIf="!selectedImage">
               点击上传图片
             </div>
+ 
           </div>
-        </ion-item>
+        </ion-item> -->
       </ion-list>
+
     </ion-card>
   </div>
+  <comp-uploader-hwobs [url]="uploadUrl" (onUrlChange)="onUrlChange($event)"></comp-uploader-hwobs>
+        @if(uploadUrl){
+         <!-- <span>已上传:{{uploadUrl}}</span> -->
+         <img [src]="uploadUrl">
+       }
   <ion-button (click)="createAgent()" expand="block" color="primary" >创建智能体</ion-button>
   
 

+ 22 - 0
novel-app/src/app/agent-create/agent-create.page.scss

@@ -0,0 +1,22 @@
+/* 全局样式 */
+ion-app {
+    background: url('/assets/images/background-image6.jpg') no-repeat center center fixed;
+    /* 添加背景图片 */
+    background-size: cover;
+    /* 背景图片覆盖整个内容区域 */
+    background-position: center;
+    /* 背景图片居中 */
+    height: 100vh;
+    /* 确保背景覆盖整个视口高度 */
+    margin: 0;
+    /* 移除默认的外边距 */
+    padding: 0;
+    /* 移除默认的内边距 */
+}
+
+
+ion-content {
+    --background: transparent;
+    /* 设置为透明,以便背景从 ion-app 继承 */
+    padding: 16px;
+}

+ 13 - 5
novel-app/src/app/agent-create/agent-create.page.ts

@@ -1,9 +1,11 @@
 import { Component, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { CloudObject,CloudUser } from '../lib/ncloud';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, ModalController, IonTextarea, IonInput, IonCard, IonItem, IonList, IonLabel, AlertController, IonButtons, IonBackButton } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, ModalController, IonTextarea, IonInput, IonCard, IonItem, IonList, IonLabel, AlertController, IonButtons, IonBackButton, IonAvatar, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, 
+  IonIcon, IonProgressBar, IonSelect, IonSelectOption, IonText, IonThumbnail } from '@ionic/angular/standalone';
 import { CommonModule } from '@angular/common';
-import {DalleOptions, FmodeChatCompletion } from 'fmode-ng';
+import { AvatarModule, ChatPanelOptions, DalleOptions, FmodeChat, FmodeChatCompletion, FmodeChatMessage, ImagineWork, openChatPanelModal } from 'fmode-ng';
+import { CompUploaderHwobsComponent } from "../comp-uploader-hwobs/comp-uploader-hwobs.component";
 
 @Component({
   selector: 'app-agent-create',
@@ -13,8 +15,9 @@ import {DalleOptions, FmodeChatCompletion } from 'fmode-ng';
   imports: [
     IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput,
     IonCard, IonBackButton,
-    IonItem, IonList, CommonModule, IonLabel,
-    IonButtons
+    IonItem, IonList, CommonModule,
+    IonButtons,
+    CompUploaderHwobsComponent
 ]
 })
 export class AgentCreatePage implements OnInit {
@@ -35,6 +38,11 @@ export class AgentCreatePage implements OnInit {
   }
   ngOnInit() {
 
+  }
+  uploadUrl:string = ""
+  onUrlChange(ev:any){
+    console.log(ev);
+   this.uploadUrl=ev;
   }
   name: string = ''
   nameInput(e:any) {
@@ -87,7 +95,7 @@ export class AgentCreatePage implements OnInit {
                     gender:`${this.gender}`,
                     desc:`${this.desc}`,
                     username: this.currentUser.data["username"],
-                    avatar: this.selectedImage,
+                    avatar: this.uploadUrl,
                     user:this.currentUser.toPointer(), 
                   })
                   consult.save();

+ 4 - 0
novel-app/src/app/app.routes.ts

@@ -75,6 +75,10 @@ export const routes: Routes = [
   {
     path: 'tab7',
     loadComponent: () => import('./tab7/tab7.page').then( m => m.Tab7Page)
+  },
+  {
+    path: 'comp-uploader-hwobs',
+    loadComponent: () => import('../app/comp-uploader-hwobs/comp-uploader-hwobs.component').then( m => m.CompUploaderHwobsComponent)
   }
 
 

+ 5 - 1
novel-app/src/app/atest/atest.page.html

@@ -27,5 +27,9 @@
   </div>
   <ion-button (click)="createAgent()" expand="block" color="primary" >创建智能体</ion-button>
   
-
+  <h1>上传组件示例</h1>
+    <comp-uploader-hwobs [url]="uploadUrl" (onUrlChange)="onUrlChange($event)"></comp-uploader-hwobs>
+    @if(uploadUrl){
+     <span>已上传:{{uploadUrl}}</span>
+   }
 </ion-content>

+ 10 - 3
novel-app/src/app/atest/atest.page.ts

@@ -4,7 +4,7 @@ import { CloudObject, CloudQuery, CloudUser } from '../lib/ncloud';
 import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton,IonIcon, ModalController, IonTextarea, IonInput, IonCard, IonCardHeader, IonCardTitle, IonThumbnail, IonCardContent, IonCardSubtitle, IonItem, IonList, IonLabel, IonAvatar, IonSelect, IonSelectOption, AlertController, IonButtons, IonProgressBar, IonText } from '@ionic/angular/standalone';
 import { CommonModule } from '@angular/common';
 import { AvatarModule, ChatPanelOptions, DalleOptions, FmodeChat, FmodeChatCompletion, FmodeChatMessage, ImagineWork, openChatPanelModal } from 'fmode-ng';
-
+import { CompUploaderHwobsComponent } from '../comp-uploader-hwobs/comp-uploader-hwobs.component';
 @Component({
   selector: 'app-atest',
   templateUrl: './atest.page.html',
@@ -15,7 +15,7 @@ import { AvatarModule, ChatPanelOptions, DalleOptions, FmodeChat, FmodeChatCompl
     IonIcon,IonCard,IonCardHeader,IonCardTitle,
     IonCardSubtitle,IonCardContent, IonThumbnail, IonItem,IonList,CommonModule,IonLabel,
     IonAvatar, IonSelect, IonSelectOption,IonButtons,IonProgressBar,
-    IonText, IonCardHeader, IonCardSubtitle,
+    IonText, IonCardHeader, IonCardSubtitle,CompUploaderHwobsComponent
   ]
 })
 export class AtestPage implements OnInit {
@@ -24,7 +24,7 @@ export class AtestPage implements OnInit {
   constructor(
     private modalCtrl:ModalController,
     private router:Router,
-    private alertController: AlertController
+    private alertController: AlertController,
   ) {
    this.currentUser = new CloudUser();
   // 示例任务,自己生成图片后请存储新的ID 
@@ -40,6 +40,12 @@ export class AtestPage implements OnInit {
   name: string = ''
   nameInput(e:any) {
     this.name = e.detail.value;
+  }
+    // 上传组件
+  uploadUrl:string = ""
+  onUrlChange(ev:any){
+    console.log(ev);
+   this.uploadUrl=ev;
   }
   age: number = 25
   ageInput(e:any) {
@@ -78,6 +84,7 @@ export class AtestPage implements OnInit {
             gender: `${this.gender}`,
             desc: `${this.desc}`,
             user: this.currentUser.toPointer(),
+            avatar: this.uploadUrl
           });
                   consult.save();
                   console.log(consult);

+ 6 - 9
novel-app/src/app/chapter-generator/chapter-generator.page.html

@@ -26,7 +26,7 @@
 
     <!-- 侧边栏 -->
     <div [style.display]="isSideShow ? 'flex' : 'none'"
-      style="display: flex; width: 200px; flex-direction: column; z-index: 1000;">
+      style="display: flex; width: 200px; height: 90%; flex-direction: column; z-index: 1000; overflow-y: auto; max-height: 100%;">
       <ion-list>
         <ion-item *ngFor="let chapter of chapters; let i = index;" (click)="selectChapter(i)">
           <ion-label>{{ chapter.title }}</ion-label>
@@ -40,11 +40,7 @@
       <ion-button (click)="addChapter()">
         <ion-icon name="add"></ion-icon>添加章节
       </ion-button>
-
-      <ion-button color="light" (click)="toggleSide()">
-        <ion-icon name="add"></ion-icon>{{isSideShow ? '折叠' : '展开'}}
-      </ion-button>
-    </div>
+</div>
 
     <!-- 内容区域 -->
     <div class="chapter-edit-container">
@@ -62,16 +58,17 @@
         <textarea [(ngModel)]="selectedChapterContent" style="width: 100%; height: 300px;"></textarea>
       </ion-item>
       <ion-button (click)="saveChapter()" expand="block" color="success">保存章节</ion-button>
-      } @else {
+      } 
+      @else {
       <p>请选择一个章节进行编辑。</p>
       }
     </div>
   </div>
 
   <!-- 悬浮球 -->
-  <ion-fab vertical="bottom" horizontal="start" slot="fixed">
+  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
     <ion-fab-button (click)="toggleSide()">
-      <ion-icon name="chevron-forward"></ion-icon>
+      <ion-icon name="add"></ion-icon>
     </ion-fab-button>
   </ion-fab>
 </ion-content>

+ 5 - 1
novel-app/src/app/chapter-generator/chapter-generator.page.scss

@@ -6,7 +6,9 @@ ion-menu {
 ion-list {
     margin-top: 16px;
 }
-
+ion-content {
+    overflow: auto; // 确保内容可以滚动,不会裁剪 FAB
+}
 .overlay {
     position: fixed;
     top: 0;
@@ -41,5 +43,7 @@ ion-list {
 
 ion-fab {
     z-index: 1000;
+    margin-left: 16px; // 根据需要调整
+    margin-bottom: 40px; // 根据需要调整
     /* 确保悬浮球在最上层 */
 }

+ 8 - 12
novel-app/src/app/chapter-generator/chapter-generator.page.ts

@@ -3,6 +3,7 @@ import { Component } from '@angular/core';
 import { FormsModule } from '@angular/forms'; // 导入 FormsModule
 import { IonRouterOutlet } from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
+import { add } from 'ionicons/icons';
 import { chevronForward } from 'ionicons/icons';
 import { IonicModule, ModalController } from '@ionic/angular';
 import { CommonModule } from '@angular/common';
@@ -30,8 +31,8 @@ addIcons({ chevronForward });
 })
 export class ChapterGeneratorPage implements OnInit {
   chapters = [
-    { title: 'Chapter 1', content: '这是第一章的内容。' },
-    // 其他章节...
+    { title: 'Chapter 1', content: '' },
+   
   ];
   isSideShow: boolean = true;
   selectedChapterIndex: number | null = null;
@@ -41,7 +42,7 @@ export class ChapterGeneratorPage implements OnInit {
   title: string = '';
   description: string = '';
 
-  constructor(private modalCtrl: ModalController, private route: ActivatedRoute) { }
+  constructor(private modalCtrl: ModalController, private route: ActivatedRoute) {  addIcons({ add });}
 
   ngOnInit() {
     this.route.queryParams.subscribe(params => {
@@ -112,15 +113,10 @@ export class ChapterGeneratorPage implements OnInit {
   }
 
   saveChapter() {
-    if (this.selectedChapterIndex !== null) {
-      this.chapters[this.selectedChapterIndex].title = this.selectedChapterTitle;
-      this.chapters[this.selectedChapterIndex].content = this.selectedChapterContent;
-      console.log('章节内容已保存:', this.selectedChapterContent);
-      this.selectedChapterIndex = null;
-      this.selectedChapterTitle = '';
-      this.selectedChapterContent = '';
-      this.isSideShow = true; // 显示侧边栏
-    }
+   
+
+
+    
   }
 
   insertTextIntoChapterEnd(text: string) {

+ 3 - 0
novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.html

@@ -0,0 +1,3 @@
+<h1>请选择文件:</h1>
+<input type="file" multiple (change)="onFileChange($event)"/>
+<button (click)="upload()">上传</button>

+ 0 - 0
novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.scss


+ 22 - 0
novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.spec.ts

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

+ 66 - 0
novel-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.ts

@@ -0,0 +1,66 @@
+import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';
+import { HwobsProvider } from '../hwobs.service';
+
+@Component({
+  selector: 'comp-uploader-hwobs',
+  templateUrl: './comp-uploader-hwobs.component.html',
+  styleUrls: ['./comp-uploader-hwobs.component.scss'],
+  standalone: true,
+})
+export class CompUploaderHwobsComponent  implements OnInit {
+
+  @Input() url:string = "";
+  @Output() onUrlChange:EventEmitter<string> = new EventEmitter<string>()
+
+  uploader:HwobsProvider|undefined
+  constructor() { }
+
+  ngOnInit() {
+    this.uploader = new HwobsProvider({
+      bucketName:"nova-cloud",
+      prefix:"dev/jxnu/storage/",
+      host:"https://app.fmode.cn/",
+      access_key_id:"XSUWJSVMZNHLWFAINRZ1",
+      secret_access_key:"P4TyfwfDovVNqz08tI1IXoLWXyEOSTKJRVlsGcV6"
+    });
+  }
+
+  file:File|undefined
+  fileData:any = ""
+  fileList:File[] = []
+
+  async upload(){
+    let filename = this.file?.name;
+    let dateStr = `${new Date().getFullYear()}${new Date().getMonth()+1}${new Date().getDate()}`;
+    let hourStr = `${new Date().getHours()}${new Date().getMinutes()+1}${new Date().getSeconds()}`;
+    let key = `${dateStr}/${hourStr}-${filename}`;
+    // let key = `storage/${filename}`
+    if(this.file){
+      let attachment = await this.uploader?.uploadFile(this.file,key);
+      console.log(attachment);
+      this.url = attachment?.get("url");
+      this.onUrlChange.emit(this.url);
+    }
+  }
+  /**
+   * 文件选择器 选择文件触发事件
+   * @param event 
+   */
+  async onFileChange(event:any){
+    console.log(event)
+    // 将选择的文件列表,赋值给fileList
+    this.fileList = event?.target?.files;
+    // 默认将第一个文件,显示在展示区域
+    this.setFile(event?.target?.files?.[0]);
+  }
+
+  /**
+   * 设置展示区域文件
+   * @param file 
+   */
+  async setFile(file:any){
+    // 将文件设置为展示区域文件
+    this.file = file
+  }
+
+}

+ 3 - 7
novel-app/src/app/component/article-card/article-card.component.html

@@ -1,11 +1,7 @@
 <div class="card">
-  <!-- <img [src]="card.get('image')[0]" alt="Image"> -->
   <div class="content">
     <h3>{{ card.get('title') }}</h3>
-    
-    <p>作者:{{ card.get('username') }}</p> <p>{{ card.get('date') }}</p>
-    <!-- <p>
-      阅读量: {{ card.get('views') }} &nbsp; 赞: {{ card.get('likes') }}
-    </p> -->
+    <p *ngIf="user.id === admin">作者:{{ card.get('username') }}</p>
+    <p>{{ card.get('date') }}</p>
   </div>
-</div>
+</div>

+ 17 - 8
novel-app/src/app/component/article-card/article-card.component.ts

@@ -1,7 +1,7 @@
 import { CommonModule } from '@angular/common';
 import { Component, Input, OnInit } from '@angular/core';
 import { IonCard } from '@ionic/angular/standalone';
-
+import { CloudObject, CloudQuery,CloudUser } from '../../lib/ncloud';
 @Component({
   selector: 'app-article-card',
   templateUrl: './article-card.component.html',
@@ -12,18 +12,27 @@ import { IonCard } from '@ionic/angular/standalone';
   ]
 })
 export class ArticleCardComponent  implements OnInit {
+  user : CloudUser 
+  admin:string = 'nRDdxdEn2k'
+  constructor() { 
+    this.user = new CloudUser();
+  }
+  async loadDoctorList(){
+    let user = new CloudUser();
+    let query = new CloudQuery("NovelCharacter");
+    query.equalTo("user",user?.id)
+    console.log("user",user.id)
+   
+  }
+  ngOnInit() {
+    this.loadDoctorList();
+  }
 
-  constructor() { }
-
-  ngOnInit() {}
-  /**
-   * controller for dropdown visibility
-   */
   dropdownVisible = false;
 
   toggleDropdown() {
     this.dropdownVisible = !this.dropdownVisible;
   }
 
-  @Input() card: any; // 接收父组件传递的卡片数据
+  @Input() card: any; 
 }

+ 1 - 1
novel-app/src/app/home/home.page.scss

@@ -1,6 +1,6 @@
 /* 全局样式 */
 ion-app {
-    background: url('/assets/images/background-image1.jpg') no-repeat center center fixed;
+    background: url('/assets/images/background-image6.jpg') no-repeat center center fixed;
     /* 添加背景图片 */
     background-size: cover;
     /* 背景图片覆盖整个内容区域 */

+ 166 - 0
novel-app/src/app/hwobs.service.ts

@@ -0,0 +1,166 @@
+import { Injectable } from '@angular/core';
+
+// @ts-ignore
+import ObsClient from "esdk-obs-browserjs"
+
+import Parse from "parse";
+
+/**
+ * HwobsDir 华为OBS目录接口
+ * @public
+ */
+export interface HwobsDir{
+  Prefix:string  // "storage/2023/"
+
+}
+
+/**
+ * HwobsDir 华为OBS文件接口
+ * @public
+ */
+export interface HwobsFile{
+  ETag: "\"f0ec968fe51ab48348307e06476122eb\""
+  Key:string  //"storage/3mkf41033623275.png"
+  LastModified:string //"2023-11-08T12:03:13.008Z"
+  Owner:object // {ID: '09971a1979800fb60fbbc00ada51f7e0'}
+  Size:string //"25839"
+  StorageClass:string //"STANDARD"
+}
+
+/**
+ * HwobsProvider 华为OBS文件服务
+ * @public
+ */
+export class HwobsProvider {
+  obsClient:ObsClient
+  bucketName:string
+  host:string
+  globalPrefix:string = ""
+  constructor(options:{
+    host:string
+    bucketName:string
+    access_key_id:string
+    secret_access_key:string
+    prefix?:string
+    server?:string
+  }) {
+      this.globalPrefix = options.prefix || ""
+      this.host = options?.host
+      this.bucketName = options?.bucketName
+      this.obsClient = new ObsClient({
+        access_key_id: options.access_key_id,       
+        secret_access_key: options.secret_access_key,
+        // 这里以华南-广州为例,其他地区请按实际情况填写
+        server: options?.server||'https://obs.cn-south-1.myhuaweicloud.com'
+    });
+  }
+
+  /**
+   * 目录及检索相关函数
+   */
+  listDir(prefix:any):Promise<{
+    dirs:Array<HwobsDir>,
+    files:Array<HwobsFile>
+  }>{
+    return new Promise((resolve,reject)=>{
+        this.obsClient.listObjects({
+          Bucket : this.bucketName,
+          Prefix : prefix,
+          Delimiter: '/'
+        }, (err:any, result:any) => {
+          if(err){              
+            console.error('Error-->' + err);   
+            reject(err)    
+          }else{              
+            console.log('Status-->' + result.CommonMsg.Status);
+            console.log(result)
+                    if(result.CommonMsg.Status < 300 && result.InterfaceResult){
+                          for(var j in result.InterfaceResult.Contents){
+                                    console.log('Contents[' + j +  ']:');
+                                    console.log('Key-->' + result.InterfaceResult.Contents[j]['Key']);
+                                    console.log('Owner[ID]-->' + result.InterfaceResult.Contents[j]['Owner']['ID']);
+                            }
+                    }       
+                    let dirs:HwobsDir[] = result.InterfaceResult.CommonPrefixes
+                    let files:HwobsFile[] = result.InterfaceResult.Contents
+                    resolve({dirs:dirs,files:files})     
+          }
+        });
+      })
+
+  }
+
+  /**
+   * 文件上传相关函数
+   * @param file 
+   * @param key 
+   * @returns 
+   */
+  async uploadFile(file:File,key:string):Promise<Parse.Object>{
+    console.log(this.globalPrefix,key)
+    // key 文件上传后的全部路径
+    // /storage/<公司账套>/<应用名称>/年月日/<文件名>.<文件后缀>
+    // /storage/web2023/<学号>/年月日/<文件名>.<文件后缀>
+    let attach = await this.checkFileExists(file);
+    if(attach?.id) return attach
+      return new Promise((resolve,reject)=>{
+        this.obsClient.putObject({
+              Bucket : this.bucketName,
+              Key : this.globalPrefix+key,
+              SourceFile : file
+        },  async (err:any, result:any) => {
+              if(err){
+                    console.error('Error-->' + err);
+                    reject(err)
+              }else{
+                console.log('Status-->' + result.CommonMsg.Status);
+                let attach = await this.saveAttachment(file,this.globalPrefix+key)
+                resolve(attach)
+              }
+        });
+      })
+  }
+  Attachment = Parse.Object.extend("Attachment")
+
+  async checkFileExists(file:any):Promise<Parse.Object>{
+    let hash = await this.getFileHash(file)
+    // 文件HASH查重,避免重复上传
+    let attach:Parse.Object
+    let query = new Parse.Query("Attachment")
+    query.equalTo("hash",hash);
+    query.equalTo("size",file.size);
+    let exists:any = await query.first();
+    if(!exists?.id) exists = new this.Attachment()
+    attach = exists
+    return attach
+  }
+  async saveAttachment(file:File,key:string){
+    console.log("saveAttachment",key)
+    let hash = await this.getFileHash(file)
+    let attach = await this.checkFileExists(file)
+    attach.set("name",file.name)
+    attach.set("size",file.size)
+    attach.set("mime",file.type)
+    attach.set("url",this.host + key)
+    attach.set("hash",hash)
+    attach = await attach.save()
+    return attach
+  }
+
+  async getFileHash(file:File) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = async (event:any) => {
+        const buffer = event.target.result;
+        const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
+        const hashArray = Array.from(new Uint8Array(hashBuffer));
+        const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
+        resolve(hashHex);
+      };
+      reader.onerror = (event:any) => {
+        reject(event.target.error);
+      };
+      reader.readAsArrayBuffer(file);
+    });
+  }
+}

+ 2 - 2
novel-app/src/app/short-generator/short-generator.page.html

@@ -12,7 +12,7 @@
   <h1>标题</h1>
   <ion-input [value]="titlel" placeholder="输入标题" (ionInput)="titleInput($event)"></ion-input>
   <h1>要求</h1>
-  <ion-input [value]="style" (ionInput)="styleInput($event)"></ion-input>
+   <ion-input [value]="style" placeholder="输入小说要求" (ionInput)="styleInput($event)"></ion-input>
 
   <!-- 词条列表编辑区域 -->
   <span (click)="showEntryList()">
@@ -25,7 +25,7 @@
 
   <!-- 展示:返回消息内容 -->
   <!-- 实时预览生成的小说大纲 -->
-  <ion-textarea [(ngModel)]="generatedOutline" placeholder="生成的小说大纲" autoGrow="true"
+  <ion-textarea [(ngModel)]="generatedOutline" placeholder="生成的小说大纲" autoGrow="true" 
     class="generated-outline"></ion-textarea>
   <!-- 按钮:执行消息生成函数 -->
   <ion-button (click)="sendOutline()" expand="block">生成小说</ion-button>

+ 14 - 16
novel-app/src/app/tab2/tab2.page.html

@@ -1,5 +1,6 @@
 <ion-header [translucent]="true">
   <ion-toolbar class="custom-toolbar">
+    
     <div class="search-bar" >
       <ion-searchbar 
       placeholder="搜索" 
@@ -7,30 +8,26 @@
       (ionInput)="searchProducts($event)">
       </ion-searchbar>
     </div>
+    
     @if(!searchTerm){
     <div class="header">
       <ion-card-header>
         <ion-card-title>
           <ion-segment [scrollable]="true" value="hotdot" [value]="type" (ionChange)="typeChange($event)">
-            <ion-segment-button value="hotdot" content-id="hotdot">
+            <!-- <ion-segment-button value="hotdot" content-id="hotdot">
               <ion-label>热点</ion-label>
             </ion-segment-button>
             
             <ion-segment-button value="export" content-id="export">
               <ion-label>专家科普</ion-label>
-            </ion-segment-button>
-            <!-- <ion-segment-button value="sleep" content-id="sleep">
-              <ion-label>睡眠</ion-label>
-            </ion-segment-button>
-            <ion-segment-button value="life" content-id="life">
-              <ion-label>生活</ion-label>
-            </ion-segment-button>
-            <ion-segment-button value="男" content-id="male">
-              <ion-label>男性</ion-label>
-            </ion-segment-button>
-            <ion-segment-button value="女" content-id="female">
-              <ion-label>女性</ion-label>
             </ion-segment-button> -->
+            <ion-segment-button value="短篇小说" content-id="短篇小说">
+              <ion-label>短篇小说</ion-label>
+             </ion-segment-button>
+             <ion-segment-button value="工具箱" content-id="短篇小说">
+              <ion-label>工具箱</ion-label>
+             </ion-segment-button>
+        
           </ion-segment>
         </ion-card-title>
       </ion-card-header>
@@ -38,7 +35,8 @@
   }
   </ion-toolbar>
 </ion-header>
-
+<!-- @if(user.id == admin){<p> llll
+</p> }  -->
 <ion-content class="knowledge" [fullscreen]="true">
   @if(!searchTerm){
     
@@ -50,8 +48,6 @@
           <ion-segment-view>
       
             <ion-segment-content id="female">
-              <!-- 轮播图区域 -->
-             
               <app-article-card (click)="openDetailModal(card)" *ngFor="let card of cards" [card]="card"></app-article-card>
             </ion-segment-content>
           </ion-segment-view>
@@ -60,6 +56,7 @@
     </div>
   }
   @if(searchTerm){
+   
     <div>
       <app-article-card (click)="openDetailModal(product)" *ngFor="let product of products" [card]="product"></app-article-card>
     </div>
@@ -101,3 +98,4 @@
 
 
 </ion-content>
+

+ 26 - 142
novel-app/src/app/tab2/tab2.page.scss

@@ -1,5 +1,18 @@
-// @import '@fortawesome/fontawesome-free/css/all.min.css'; // 字体图标
-// @import 'highlight.js/scss/monokai.scss'; // 代码高亮 - monokai主题
+/* 全局样式 */
+ion-app {
+  background: url('/assets/images/background-image1.jpg') no-repeat center center fixed; /* 设置背景图片 */
+  background-size: cover; /* 使背景图片覆盖整个区域 */
+  height: 100vh;
+  margin: 0;
+  padding: 0;
+}
+
+/* 其他样式 */
+.modal-content {
+  --background: #fff; /* 模态框内容背景 */
+  padding: 20px;
+}
+
 .custom-toolbar {
   --background: rgba(255, 255, 255, 0.8); /* 使工具栏背景透明 */
   display: flex; /* 使用 Flexbox 布局 */
@@ -8,12 +21,11 @@
   padding: 0; /* 去掉默认内边距 */
 }
 
-
 .search-bar {
-    padding: 10px;
-    text-align: center;
-  }
-  
+  padding: 10px;
+  text-align: center;
+}
+
 .custom-searchbar {
   --background: #ffffff;
   --border-radius: 20px;
@@ -22,18 +34,14 @@
 
 .header {
   height: 80px;
-  margin-top:-10px
+  margin-top: -10px;
 }
+
 .knowledge {
   height: 100%;
   width: 100%;
 }
 
-.content {
-  margin-top: -5px;
-  -height: 100%;
-  width: 100%;
-}
 ion-card-header {
   font-size: 1.5em;
   height: auto;
@@ -41,14 +49,13 @@ ion-card-header {
 
 ion-card {
   width: 100%;
-  height: 100%;
+  height: auto; /* 调整高度以适应内容 */
   margin: 0;
   padding: 0;
   border-radius: 0;
   box-shadow: none;
 }
 
-
 ion-card-content {
   font-size: 1.2em;
   width: 100%;
@@ -61,27 +68,17 @@ ion-segment-view {
 }
 
 ion-segment-content {
-  // display: flex;
   align-items: center;
   justify-content: center;
 }
 
-// ion-segment-content:nth-of-type(5) {
-//   background: lightpink;
-// }
-// ion-segment-content:nth-of-type(2) {
-//   background: lightblue;
-// }
-// ion-segment-content:nth-of-type(3) {
-//   background: lightgreen;
-// }
-
-.share-modal{
+.share-modal {
   --height: 30vh;
   --width: 100%;
   --offset-y: 0; /* 确保模态窗口从底部弹出 */
 }
-// 底部弹窗(modal)样式
+
+/* 底部弹窗(modal)样式 */
 .bottom-modal {
   --height: 100vh;
   --width: 100%;
@@ -102,117 +99,4 @@ ion-segment-content {
     object-fit: cover;
     border-radius: 8px;
   }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// .tabs {
-//   display: flex;
-//   justify-content: space-around;
-//   padding: 0px 0;
-//   background-color: #f8f8f8;
-// }
-
-// .tabs ion-button {
-//   flex: 1;
-//   text-align: center;
-//   // border: none;
-//   --background: transparent;
-//   --color-checked: #4caf50;
-//   --indicator-color: #4caf50;
-//   --color: #666;
-//   --color-focused: #4caf50;
-//   --color-hover: #4caf50;
-//   --color-activated: #4caf50;
-//   --color-selected: #4caf50;
-// }
-
-// .tab {
-//   cursor: pointer;
-//   padding: 0px 0px;
-// }
-// .tab.active {
-//   color: rgb(81, 255, 0);
-//   background-color: rgb(255, 255, 255);
-// }
-
-//
-
-// 轮播图区域
-.carousel-container {
-  position: relative;
-  max-width: 800px;
-  margin: 0 auto;
-  overflow: hidden;
-}
-
-.carousel {
-  display: flex;
-  transition: transform 0.5s ease-in-out;
-}
-
-.slide {
-  min-width: 100%;
-}
-
-.slide img {
-  width: 100%;
-  height: auto;
-}
-
-.prev, .next {
-  position: absolute;
-  top: 50%;
-  transform: translateY(-50%);
-  background: rgba(0, 0, 0, 0.5);
-  color: white;
-  padding: 16px;
-  border: none;
-  cursor: pointer;
-}
-
-.prev {
-  left: 0;
-}
-
-.next {
-  right: 0;
-}
-
-.dots {
-  position: absolute;
-  bottom: 20px;
-  left: 50%;
-  transform: translateX(-50%);
-  text-align: center;
-}
-
-.dot {
-  display: inline-block;
-  width: 10px;
-  height: 10px;
-  margin: 0 5px;
-  background: #bbb;
-  border-radius: 50%;
-  cursor: pointer;
-}
-
-.dot.active {
-  background: #717171;
-} 
-//
+}

+ 44 - 36
novel-app/src/app/tab2/tab2.page.ts

@@ -1,12 +1,12 @@
 import { MarkdownPreviewModule } from 'fmode-ng';
 import { Component } from '@angular/core';
-import { ModalController, IonModal, IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, IonLabel, IonAvatar, IonButton, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonCardContent, IonCardTitle, IonCardHeader, IonCard, IonIcon, IonButtons, IonSearchbar, IonFab, IonFabButton, IonFabList } from '@ionic/angular/standalone';
+import { ModalController, IonModal, IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, IonLabel, IonAvatar, IonButton, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonCardContent, IonCardTitle, IonCardHeader, IonCard, IonIcon, IonButtons, IonSearchbar, IonFab, IonFabButton, IonFabList, IonBackButton } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { addIcons } from 'ionicons';
 import { airplane, bluetooth, call, wifi } from 'ionicons/icons';
 import { ArticleCardComponent } from '../component/article-card/article-card.component';
 import { CommonModule } from '@angular/common';
-import { CloudObject, CloudQuery } from '../lib/ncloud';
+import { CloudObject, CloudQuery,CloudUser } from '../lib/ncloud';
 import { Router } from '@angular/router';
 import {
   chevronDownCircle,
@@ -24,27 +24,16 @@ addIcons({ chevronDownCircle, chevronForwardCircle, chevronUpCircle, colorPalett
   styleUrls: ['tab2.page.scss'],
   standalone: true,
   imports: [
-    IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
-    IonLabel,IonItem,IonList,IonAvatar,ArticleCardComponent,CommonModule,IonButton,
+    IonHeader, IonToolbar, IonTitle, IonContent,
+    IonLabel, ArticleCardComponent, CommonModule, IonButton,
     IonSegment, IonSegmentButton,
-    IonSegmentContent,IonSegmentView,IonCardContent, IonCardTitle, IonCardHeader,IonCard,
-    IonModal,IonIcon, IonButtons, IonSearchbar, IonFab, IonFabButton,IonFabList,MarkdownPreviewModule
-  ]
+    IonSegmentContent, IonSegmentView, IonCardContent, IonCardTitle, IonCardHeader, IonCard,
+    IonModal, IonButtons, IonSearchbar, MarkdownPreviewModule
+]
 })
 
 export class Tab2Page {
   
- /**
-  * 轮播图
-  */
- images = [
-  'https://picsum.photos/800/400?random=1',
-  'https://picsum.photos/800/400?random=2',
-  'https://picsum.photos/800/400?random=3',
-  'https://picsum.photos/800/400?random=4',
-  'https://picsum.photos/800/400?random=5',
-  'https://picsum.photos/800/400?random=6',
-];
 
 currentSlide = 0;
 intervalId: any;
@@ -58,18 +47,21 @@ setSlidePosition() {
 
   products: Array<CloudObject> = []; // 当前显示的科普信息
   allCards: Array<CloudObject> = []; // 所有科普信息
-
+  admin:string = 'nRDdxdEn2k'
+  user : CloudUser 
   //搜索功能
   searchTerm: string = '';
 
   async searchProducts(event: any) {
     this.searchTerm = event.detail.value.toLowerCase();
+    console.log('搜索词:', this.searchTerm);
+    
     if (this.searchTerm) {
       this.products = this.allCards.filter(product =>
-        product.get('topic').toLowerCase().includes(this.searchTerm) ||
+        product.get('username').toLowerCase().includes(this.searchTerm) ||
         product.get('title').toLowerCase().includes(this.searchTerm) ||
         product.get('category').toLowerCase().includes(this.searchTerm) ||
-        product.get('content')[0].toLowerCase().includes(this.searchTerm)
+        product.get('content2')[0].toLowerCase().includes(this.searchTerm)
       );
     } else {
       this.products = [...this.allCards]; // 如果搜索词为空,则显示所有科普信息
@@ -77,7 +69,7 @@ setSlidePosition() {
   }
 
   isModalOpen = false;
-  currentProduct: any;      // 当前选择的科普信息
+  currentProduct: any;      
 
   openDetailModal(product?: any) {
     this.isModalOpen = true;
@@ -91,41 +83,57 @@ setSlidePosition() {
 
 
 
-  type:"hotdot"|"export" = "hotdot"
+  type:"短篇小说"|"工具箱" = "短篇小说"
 content = ''
   constructor(
     private modalCtrl:ModalController,
     private router:Router,
   ) { 
-    
-    this.loadCards(); // 初始化时加载所有科普信息
+    this.user = new CloudUser();
+    this.loadmyCards(); // 初始化时加载所有科普信息
   }
 
-  cards: Array<CloudObject> = []; // 当前显示的分类卡片
+  cards: Array<CloudObject> = [];
+  mycards: Array<CloudObject> = []; // 当前显示的分类卡片
+  myCards: Array<CloudObject> = [];
   async typeChange(ev: any) {
-    this.type = ev?.detail?.value || ev?.value || 'hotdot';
+    this.type = ev?.detail?.value || ev?.value || '短篇小说';
     console.log(this.type);
-    await this.loadCards(); // 重新加载卡片
+    await this.loadmyCards(); // 重新加载卡片
   }
 
-  async loadCards() {
+  // async loadCards() {
+  //   const query = new CloudQuery('NovelAriticle');
+  //   this.allCards = await query.find(); 
+  //   console.log(this.allCards);
+  //   this.cards = this.allCards.filter((card) => card.get('category').toLowerCase().includes(this.type));
+  //   console.log(this.cards);
+  // }
+  async loadmyCards() {
+    let user = new CloudUser();
     const query = new CloudQuery('NovelAriticle');
-    // this.content = '',
+    if(user.id == this.admin){   
+      this.allCards = await query.find(); 
+    console.log(this.allCards);
+    this.cards = this.allCards.filter((card) => card.get('category').toLowerCase().includes(this.type));
+    console.log(this.cards);
+
+     }else{
+    query.equalTo("user",user?.id)
+    console.log("user",user.id)
     this.allCards = await query.find(); 
     console.log(this.allCards);
     this.cards = this.allCards.filter((card) => card.get('category').toLowerCase().includes(this.type));
     console.log(this.cards);
-   
+  }
   }
 
 
 
 
-  openAiKnowledge(){
-    this.router.navigate(['tabs/ai-knowledge']);
-  }
+ 
   ngOnInit() {
-
+    this.loadmyCards();
   }
 
-}
+}

+ 3 - 1
novel-app/src/app/tab4/tab4.page.html

@@ -8,7 +8,9 @@
     </ion-title>
   </ion-toolbar>
 </ion-header>
-<ion-content [fullscreen]="true">
+<ion-content [fullscreen]="true" class="ion-padding"
+  style="background-image: url('../../assets/images/background-image6.jpg'); background-size: cover; background-position: center; background-repeat: no-repeat;">
+
 
   <ion-refresher slot="fixed" (ionRefresh)="handleRefresh($event)">
     <ion-refresher-content></ion-refresher-content>

+ 24 - 6
novel-app/src/app/tab4/tab4.page.scss

@@ -1,3 +1,26 @@
+/* 全局样式 */
+ion-app {
+  background: url('/assets/images/background-image6.jpg') no-repeat center center fixed;
+  /* 添加背景图片 */
+  background-size: cover;
+  /* 背景图片覆盖整个内容区域 */
+  background-position: center;
+  /* 背景图片居中 */
+  height: 100vh;
+  /* 确保背景覆盖整个视口高度 */
+  margin: 0;
+  /* 移除默认的外边距 */
+  padding: 0;
+  /* 移除默认的内边距 */
+}
+
+
+ion-content {
+  --background: transparent;
+  /* 设置为透明,以便背景从 ion-app 继承 */
+  padding: 16px;
+}
+
 .custom-toolbar {
   --background: rgba(255, 255, 255, 0.8); /* 使工具栏背景透明 */
   display: flex; /* 使用 Flexbox 布局 */
@@ -97,12 +120,7 @@ ion-card:hover {
   flex: 1; /* 使用户信息部分占据剩余空间 */
 }
 
-ion-content {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 68vh; /* 使内容区域高度为视口高度 */
-}
+
 
 .login-card {
   width: 94%; /* 可以根据需要调整宽度 */

+ 5 - 0
novel-app/src/app/tabs/tabs.page.html

@@ -10,6 +10,11 @@
       <ion-label>角色</ion-label>
     </ion-tab-button>
 
+    <ion-tab-button tab="tab2" (click)="goPage('tabs/tab2')">
+      <ion-icon aria-hidden="true" name="search"></ion-icon>
+      <ion-label>生成管理</ion-label>
+    </ion-tab-button>
+
     <ion-tab-button tab="person" (click)="goPage('/tabs/person')">
       <ion-icon aria-hidden="true" name="person"></ion-icon>
       <ion-label>个人</ion-label>

+ 2 - 2
novel-app/src/app/tabs/tabs.page.ts

@@ -2,7 +2,7 @@ import { Component, EnvironmentInjector, inject } from '@angular/core';
 import { Router } from '@angular/router';
 import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, NavController } from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
-import { home, chatbox, person } from 'ionicons/icons';
+import { home, chatbox, person, search } from 'ionicons/icons';
 
 @Component({
   selector: 'app-tabs',
@@ -18,7 +18,7 @@ export class TabsPage {
     private navCtrl: NavController,
     private router: Router
   ) {
-    addIcons({ home, chatbox, person });
+    addIcons({ home, chatbox, person, search });
   }
   goPage(page: string) {
     this.router.navigateByUrl(page)

+ 5 - 0
novel-app/src/app/tabs/tabs.routes.ts

@@ -21,6 +21,11 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../tab1/tab1.page').then((m) => m.Tab1Page),
       },
+      {
+        path: 'tab2',
+        loadComponent: () =>
+          import('../tab2/tab2.page').then((m) => m.Tab2Page),
+      },
       {
         path: 'story-generator',
         loadComponent: () => import('../story-generator/story-generator.page').then(m => m.StoryGeneratorPage)

BIN
novel-app/src/assets/images/background-image6.jpg