Browse Source

Merge branch 'master' of http://git.fmode.cn:3000/yuebuzu/s202226701018

yuebuzu-creater 3 months ago
parent
commit
5d044f7a26

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

@@ -26,4 +26,8 @@ export const routes: Routes = [
     path: 'drug-category',
     loadComponent: () => import('./drug-category/drug-category.page').then( m => m.DrugCategoryPage)
   },
+  {
+    path: 'comp-uploader-hwobs',
+    loadComponent: () => import('../app/comp-uploader-hwobs/comp-uploader-hwobs.component').then( m => m.CompUploaderHwobsComponent)
+  }
 ];

+ 22 - 0
wisdom-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.html

@@ -0,0 +1,22 @@
+<ion-header [translucent]="true">
+  <ion-toolbar class="custom-toolbar">
+    <ion-button slot="start" size="small" (click)="backHome()" color="primary">back</ion-button>
+    <ion-title class="custom-title">
+      本地头像上传
+    </ion-title>
+  </ion-toolbar>
+</ion-header>
+<ion-content [fullscreen]="true">
+  <div class="div1">
+    <div class="div2">
+      <h1>请选择本地图片:</h1>
+      <input type="file" multiple (change)="onFileChange($event)"/>
+      <button (click)="upload()">上传</button>
+
+      <p *ngIf="url">
+        上传图片地址(复制此地址至头像编辑处即可): 
+        <span class="upload-url" >{{ url }}</span>
+      </p>
+    </div>
+  </div>
+</ion-content>

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

@@ -0,0 +1,112 @@
+.custom-toolbar {
+    --background: rgba(255, 255, 255, 0.8); /* 使工具栏背景透明 */
+    display: flex; /* 使用 Flexbox 布局 */
+    justify-content: center; /* 水平居中 */
+    align-items: center; /* 垂直居中 */
+    padding: 0; /* 去掉默认内边距 */
+}
+
+.custom-title {
+    font-size: 1.3em; /* 字体大小 */
+    font-weight: bold; /* 加粗 */
+    color: #000000; 
+    text-align: center; /* 文字居中对齐 */
+    margin: 0; /* 去掉默认外边距 */
+    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); /* 添加文字阴影效果 */
+    /* 添加其他美化效果 */
+    font-family: "微软雅黑"; /* 自定义字体 */
+}
+
+/* 容器样式 */
+.div1{
+    width: 100%;
+    height: 100%;
+    background-image: url('https://app.fmode.cn/dev/jxnu/202226701019/zhuti1.jpg');
+    background-size: cover;
+    background-position: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.div2 {
+    opacity: 0.9;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column; /* 垂直排列 */
+    padding: 20px;
+    border: 1px solid #ccc;
+    border-radius: 8px;
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+    background-color: #f9f9f9;
+    width: 600px; /* 限制最大宽度 */
+    margin: 0 auto; /* 居中 */
+  }
+  
+  /* 标题样式 */
+  h1 {
+    font-size: 24px;
+    margin-bottom: 20px; /* 标题与文件输入之间的间距 */
+    color: #333;
+  }
+  
+  /* 文件输入框样式 */
+  input[type="file"] {
+    margin-bottom: 20px; /* 文件输入与上传按钮之间的间距 */
+    padding: 10px;
+    border: 1px solid #007bff;
+    border-radius: 5px;
+    font-size: 16px;
+    color: #555;
+  }
+  
+  /* 上传按钮样式 */
+  button {
+    padding: 10px 20px;
+    background-color: #007bff; /* 按钮背景色 */
+    color: white; /* 按钮文字颜色 */
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;
+    font-size: 16px;
+    transition: background-color 0.3s; /* 添加过渡效果 */
+  }
+  
+  /* 上传按钮悬停效果 */
+  button:hover {
+    background-color: #0056b3; /* 悬停时的背景色 */
+  }
+  
+  /* 响应式设计 */
+  @media (max-width: 500px) {
+    div {
+      width: 90%; /* 小屏幕时宽度为90% */
+    }
+  }
+
+  /* 上传文件地址样式 */
+.upload-url {
+    user-select: text;
+    display: block; /* 使其成为块级元素 */
+    margin-top: 10px; /* 上边距 */
+    padding: 10px; /* 内边距 */
+    border: 1px solid #007bff; /* 边框 */
+    border-radius: 5px; /* 圆角 */
+    background-color: #f1f1f1; /* 背景颜色 */
+    color: #007bff; /* 字体颜色 */
+    text-decoration: none; /* 去掉下划线 */
+    overflow-wrap: break-word; /* 允许单词换行 */
+    word-wrap: break-word; /* 兼容性 */
+    white-space: normal; /* 正常的空白处理 */
+  }
+  
+  /* 链接悬停样式 */
+  .upload-url:hover {
+    background-color: #e0e0e0; /* 悬停时的背景色 */
+  }
+  
+  /* 链接被选中时的样式 */
+  .upload-url::selection {
+    background-color: #007bff; /* 选中背景色 */
+    color: white; /* 选中文字颜色 */
+  }

+ 22 - 0
wisdom-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();
+  });
+});

+ 82 - 0
wisdom-app/src/app/comp-uploader-hwobs/comp-uploader-hwobs.component.ts

@@ -0,0 +1,82 @@
+import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';
+import { HwobsProvider } from '../../app/hwobs.service';
+import { CommonModule } from '@angular/common';
+import { IonButton, IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+import { Router } from '@angular/router';
+
+@Component({
+  selector: 'comp-uploader-hwobs',
+  templateUrl: './comp-uploader-hwobs.component.html',
+  styleUrls: ['./comp-uploader-hwobs.component.scss'],
+  standalone: true,
+  imports:[
+    CommonModule,
+    IonHeader,
+    IonToolbar,
+    IonButton,
+    IonTitle,
+    IonContent
+  ]
+})
+export class CompUploaderHwobsComponent  implements OnInit {
+
+  @Input() url:string = "";
+  @Output() onUrlChange:EventEmitter<string> = new EventEmitter<string>()
+
+  uploader:HwobsProvider|undefined
+  constructor(
+    private router: Router
+  ) { }
+
+  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
+  }
+
+  backHome(){
+    this.router.navigate(['/tabs/tab4']);
+  }
+}

+ 166 - 0
wisdom-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
wisdom-app/src/app/tab4/tab4.page.html

@@ -144,8 +144,8 @@
             <ion-label (click)="goToHealth()">健康档案</ion-label>
           </ion-item>
           <ion-item>
-            <ion-icon name="location" slot="start"></ion-icon>
-            <ion-label>我的地址</ion-label>
+            <ion-icon name="images" slot="start" ></ion-icon>
+            <ion-label (click)="goToLocalAvatar()">本地头像</ion-label>
           </ion-item>
           <ion-item>
             <ion-icon name="key" slot="start"></ion-icon>

+ 5 - 0
wisdom-app/src/app/tab4/tab4.page.ts

@@ -86,6 +86,11 @@ export class Tab4Page {
     this.router.navigate(['/tabs/weather-time'])
   }
 
+  //前往本地图片上传
+  goToLocalAvatar(){
+    this.router.navigate(['comp-uploader-hwobs'])
+  }
+
   //用户
   currentUser:CloudUser|undefined
   constructor(

+ 55 - 0
wisdom-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss

@@ -0,0 +1,55 @@
+/* 容器样式 */
+ion-item {
+    display: flex;
+    align-items: center; /* 垂直居中 */
+    padding: 10px 15px; /* 内边距 */
+    border: 1px solid #e0e0e0; /* 边框 */
+    border-radius: 8px; /* 圆角 */
+    margin-bottom: 15px; /* 项目之间的间距 */
+    background-color: #fff; /* 背景颜色 */
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 阴影效果 */
+  }
+  
+  /* 文件上传组件样式 */
+  comp-uploader-hwobs {
+    flex: 1; /* 让上传组件占据剩余空间 */
+    margin-right: 10px; /* 上传组件与输入框之间的间距 */
+  }
+  
+  /* 输入框样式 */
+  ion-input {
+    flex: 2; /* 让输入框占据更多空间 */
+    --padding-start: 10px; /* 输入框内左边距 */
+    --padding-end: 10px; /* 输入框内右边距 */
+    border: 1px solid #007bff; /* 输入框边框 */
+    border-radius: 5px; /* 输入框圆角 */
+    font-size: 16px; /* 字体大小 */
+    color: #555; /* 字体颜色 */
+  }
+  
+  /* 输入框占位符样式 */
+  ion-input::placeholder {
+    color: #aaa; /* 占位符颜色 */
+  }
+  
+  /* 输入框变化时的样式 */
+  ion-input:focus {
+    border-color: #0056b3; /* 聚焦时的边框颜色 */
+    box-shadow: 0 0 5px rgba(0, 86, 179, 0.5); /* 聚焦时的阴影效果 */
+  }
+  
+  /* 响应式设计 */
+  @media (max-width: 600px) {
+    ion-item {
+      flex-direction: column; /* 小屏幕时垂直排列 */
+    }
+    
+    comp-uploader-hwobs {
+      margin-right: 0; /* 移除右边距 */
+      margin-bottom: 10px; /* 上传组件与输入框之间的间距 */
+    }
+    
+    ion-input {
+      width: 100%; /* 输入框宽度为100% */
+    }
+  }

+ 8 - 3
wisdom-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts

@@ -1,8 +1,8 @@
 import { Input, OnInit } from '@angular/core';
 import { Component } from '@angular/core';
 import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel, AlertController } from '@ionic/angular/standalone';
+import { CompUploaderHwobsComponent } from 'src/app/comp-uploader-hwobs/comp-uploader-hwobs.component';
 import { CloudUser } from 'src/lib/ncloud';
-
 @Component({
   selector: 'app-modal-user-edit',
   templateUrl: './modal-user-edit.component.html',
@@ -11,11 +11,16 @@ import { CloudUser } from 'src/lib/ncloud';
   imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
     IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
     IonInput,IonItem,
-    IonSegment,IonSegmentButton,IonLabel
+    IonSegment,IonSegmentButton,IonLabel,CompUploaderHwobsComponent
   ],
 })
 export class ModalUserEditComponent  implements OnInit {
-
+  // 上传组件
+  // uploadUrl:string = ""
+  // onUrlChange(ev:any){
+  //   console.log(ev);
+  //   this.uploadUrl=ev;
+  // }
   currentUser:CloudUser|undefined
   userData:any = {}
   userDataChange(key:string,ev:any){