Procházet zdrojové kódy

feat: new comp-table-list comp

ryanemax před 9 měsíci
rodič
revize
6d536d6b4c
22 změnil soubory, kde provedl 1465 přidání a 46 odebrání
  1. 2 0
      angular.json
  2. 918 16
      package-lock.json
  3. 4 1
      package.json
  4. 7 2
      projects/textbook/src/app/app.config.ts
  5. 81 0
      projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.html
  6. 0 0
      projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.scss
  7. 23 0
      projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.spec.ts
  8. 155 0
      projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.ts
  9. 70 0
      projects/textbook/src/app/comp-table/parse-data.service.ts
  10. 3 1
      projects/textbook/src/index.html
  11. 12 0
      projects/textbook/src/modules/nav-author/modules.routes.ts
  12. 2 2
      projects/textbook/src/modules/nav-author/page-home/page-home.component.html
  13. 15 2
      projects/textbook/src/modules/nav-author/page-home/page-home.component.ts
  14. 1 0
      projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.html
  15. 0 0
      projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.scss
  16. 22 0
      projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.spec.ts
  17. 42 0
      projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.ts
  18. 64 0
      projects/textbook/src/pipes/utilnow.pipe.ts
  19. 30 0
      projects/textbook/src/schemas/EduTextbook.ts
  20. 3 1
      projects/textbook/src/styles.scss
  21. 5 19
      server/db/schemas/edu-textbook.js
  22. 6 2
      server/package-lock.json

+ 2 - 0
angular.json

@@ -37,6 +37,7 @@
               }
             ],
             "styles": [
+              "@angular/material/prebuilt-themes/azure-blue.css",
               "projects/textbook/src/styles.scss",
               "node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
               {
@@ -127,6 +128,7 @@
               }
             ],
             "styles": [
+              "@angular/material/prebuilt-themes/azure-blue.css",
               "projects/textbook/src/styles.scss"
             ],
             "scripts": []

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 918 - 16
package-lock.json


+ 4 - 1
package.json

@@ -11,14 +11,17 @@
   "private": true,
   "dependencies": {
     "@angular/animations": "^18.0.0",
+    "@angular/cdk": "^18.0.3",
     "@angular/common": "^18.0.0",
     "@angular/compiler": "^18.0.0",
     "@angular/core": "^18.0.0",
     "@angular/forms": "^18.0.0",
+    "@angular/material": "^18.0.3",
     "@angular/platform-browser": "^18.0.0",
     "@angular/platform-browser-dynamic": "^18.0.0",
     "@angular/router": "^18.0.0",
     "@ionic/angular": "^8.2.2",
+    "@ngx-translate/core": "^15.0.0",
     "@types/parse": "^3.0.9",
     "ng-zorro-antd": "^18.0.0",
     "parse": "^5.1.0",
@@ -40,4 +43,4 @@
     "karma-jasmine-html-reporter": "~2.1.0",
     "typescript": "~5.4.2"
   }
-}
+}

+ 7 - 2
projects/textbook/src/app/app.config.ts

@@ -9,8 +9,13 @@ import { FormsModule } from '@angular/forms';
 import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
 import { provideHttpClient } from '@angular/common/http';
 import { provideIonicAngular } from '@ionic/angular/standalone';
-
+import { TranslateLoader } from '@ngx-translate/core';
+// import { TranslateService } from '@ngx-translate/core';
 registerLocaleData(zh);
 export const appConfig: ApplicationConfig = {
-  providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideNzI18n(zh_CN), importProvidersFrom(FormsModule), provideAnimationsAsync(), provideHttpClient(), provideIonicAngular({})]
+  providers: [
+    provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideNzI18n(zh_CN), importProvidersFrom(FormsModule), provideAnimationsAsync(), provideHttpClient(), provideIonicAngular({}), provideAnimationsAsync(),
+    // TranslateService,
+
+  ]
 };

+ 81 - 0
projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.html

@@ -0,0 +1,81 @@
+<div style="width:100%;display: flex;justify-content: end;">
+
+  <div style="width:30%;display: flex;justify-content: end;align-items: flex-end;">
+    <ng-container *ngFor="let button of schema?.buttons">
+      <button *ngIf="button?.place=='top'&&button?.show({queryParams:queryParams})" mat-raised-button color="primary" (click)="batchHandle(button)" >{{button?.name }}</button>
+    </ng-container>
+  </div>
+</div>
+
+<nz-table #editRowTable nzBordered [nzData]="list" *ngIf="showMode=='list'"
+  [nzNoResult]="currentLang=='cn'?'暂无数据':'No Data'"
+  [nzLoading]="isLoading" nzFrontPagination="false"
+>
+    <thead>
+      <tr>
+        <ng-container *ngFor="let field of headerArray">
+            <th >{{field?.name}}</th>
+        </ng-container>
+            <th >状态</th>
+            <th >操作</th>
+    </tr>
+    </thead>
+    <tbody>
+      <ng-container *ngFor="let object of editRowTable.data">
+        <tr class="editable-row" *ngIf="object?.get('isDeleted')!=true">
+          <ng-container *ngFor="let field of headerArray">
+            <!-- 单行数据每个字段渲染 -->
+            <td > 
+              <ng-contianer [ngSwitch]="field?.type">
+                <ng-container *ngSwitchCase="'Pointer'">
+                  {{ pointerShowMap?.[object?.get(field.key)?.objectId||object?.get(field.key)?.id] || "-" }}
+                  <!-- 设备: cluster 单元切换 -->
+                  <!-- <comp-select-cluster *ngIf="field?.className=='MinerCluster' && object?.className!='MinerCluster'" [device]="object" [unit]="object?.get(field.key)"></comp-select-cluster> -->
+                </ng-container>
+                <ng-container *ngSwitchCase="'Boolean'">
+                  <ng-container *ngIf="currentLang=='cn'">
+                    <mat-slide-toggle [checked]="object.get(field.key)" (change)="toggleFiled(object,field.key)">{{object.get(field.key)?"是":"否"}}</mat-slide-toggle>
+                  </ng-container>
+                  <ng-container *ngIf="currentLang=='en'">
+                    <mat-slide-toggle [checked]="object.get(field.key)" (change)="toggleFiled(object,field.key)">{{object.get(field.key)?"Yes":"No"}}</mat-slide-toggle>
+                  </ng-container>
+                </ng-container>
+                <ng-container *ngSwitchDefault>
+                  {{ (object.get(field.key) || "-")  }}
+                  <!-- 设备: position 位置切换 -->
+                  <!-- <comp-select-position [unit]="unit" *ngIf="queryParams?.where?.type=='miner' && field.key=='position' && object?.className=='MinerDevice'" [device]="object" [position]="object?.get(field.key)"></comp-select-position> -->
+
+                </ng-container>
+              </ng-contianer>
+            </td>
+          </ng-container>
+          <td>
+            <!-- 在线时间:优先显示最后在线时间,若无则显示数据更新时间 -->
+            {{(object?.get("onlineAt") || object?.updatedAt) | utilnow:currentLang }}
+          </td>
+          <td >
+            <ng-container *ngFor="let button of schema?.buttons">
+              <button *ngIf="button?.place=='item'&&button?.show({object:object})" mat-raised-button [color]="button?.color||'primary'" (click)="buttonHandle(button,object)" >{{button?.name }}</button>
+            </ng-container>
+          </td>
+        </tr>
+      </ng-container>
+
+    </tbody>
+  </nz-table>
+
+  <div style="display:flex;flex-wrap: wrap;width:100%;" *ngIf="showMode=='grid'">
+    <ng-container *ngFor="let object of list">
+      <div style="width:33%;padding:10px;">
+        <!-- <comp-monitor-card [monitor]="object"></comp-monitor-card> -->
+      </div>
+    </ng-container>
+    <nz-empty *ngIf="!list?.length" nzNotFoundImage="simple" [nzNotFoundContent]="currentLang=='cn'?'暂无数据':'No Data'"></nz-empty>
+  </div>
+
+  <div style="width:100%;display: flex;justify-content: center;align-items: center;">
+    <nz-pagination 
+    [nzPageIndex]="pageIndex"
+    [nzPageSize]="pageSize" [nzTotal]="pageTotal" (nzPageIndexChange)="onPageIndexChange($event)"
+    ></nz-pagination>
+  </div>

+ 0 - 0
projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.scss


+ 23 - 0
projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CompTableListComponent } from './comp-table-list.component';
+
+describe('CompTableListComponent', () => {
+  let component: CompTableListComponent;
+  let fixture: ComponentFixture<CompTableListComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ CompTableListComponent ]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(CompTableListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 155 - 0
projects/textbook/src/app/comp-table/comp-table-list/comp-table-list.component.ts

@@ -0,0 +1,155 @@
+import { Component, Input,Output,EventEmitter } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import Parse from "parse";
+// import { ClusterService } from '../cluster.service';
+import { ParseDataService } from '../parse-data.service';
+// import { TranslateModule, TranslateService } from '@ngx-translate/core';
+import { CommonModule } from '@angular/common';
+
+import { NzPaginationModule } from 'ng-zorro-antd/pagination';
+import { NzEmptyModule } from 'ng-zorro-antd/empty';
+
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatButtonModule } from '@angular/material/button';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { UtilnowPipe } from '../../../pipes/utilnow.pipe';
+import { NzTableModule } from 'ng-zorro-antd/table';
+
+interface SchemaFiled{
+  key:string,
+  name:string,
+  type:string,
+  require?:boolean, // 是否必填
+  isHeader?:boolean, // 是否表头显示 
+}
+@Component({
+  selector: 'comp-table-list',
+  templateUrl: './comp-table-list.component.html',
+  styleUrls: ['./comp-table-list.component.scss'],
+  standalone:true,
+  imports:[CommonModule,
+    UtilnowPipe,
+    // TranslateModule,
+    NzPaginationModule,NzEmptyModule,NzTableModule,
+    MatButtonModule,MatCheckboxModule,MatSlideToggleModule,
+  ],
+  providers:[]
+})
+export class CompTableListComponent {
+
+  @Input()
+  schema:any
+  @Input()
+  fieldsArray:Array<any>|undefined
+  @Input()
+  className:string|undefined
+  @Input()
+  queryParams:any = {}
+  @Input()
+  showMode:"list"|"grid" = "list"
+
+  clearPosition(){
+    this.positionMap = {}
+    this.refresh();
+  }
+  positionMap:any = {}
+
+  headerArray:Array<SchemaFiled>|undefined
+  currentLang:string= "cn"
+  constructor(
+    private parseData:ParseDataService,
+    // private clusterServ:ClusterService,
+    private dialog:MatDialog,
+    // public translate:TranslateService
+  ){
+    this.currentLang = "cn"// this.translate.getDefaultLang();
+  }
+  ngOnInit(){
+    this.headerArray = this.fieldsArray?.filter(item=>item.isHeader==true)
+    this.refresh();
+  }
+
+  batchHandle(button:any){
+    // console.log(this.currentLang,'888888888888888888')
+    button?.handle({
+      // clusterServ:this.clusterServ,
+      className:this.className,
+      queryParams:this.queryParams,
+      dialog:this.dialog
+    },this.currentLang)
+  }
+
+  @Input()
+  onButtonHandleCallBack:Function|undefined
+  buttonHandle(button:any,object:any){
+    let that = this
+    button?.handle({object:object,dialog:this.dialog,callback:this.onButtonHandleCallBack},that.currentLang)
+  }
+  refresh(page?:number){
+    if(page){this.pageIndex=page}
+    this.loadData(true)
+    this.loadData()
+  }
+  list:Parse.Object[] = []
+  pageTotal = 100;
+  pageSize = 12;
+  pageIndex = 1; // 从第1页开始计数
+  onPageIndexChange(pageIndex:any){
+    this.pageIndex = pageIndex
+    this.refresh();
+  }
+  isLoading:boolean = false;
+  async loadData(isCount:boolean=false){
+    if(!this.className) return 
+    let query = Parse.Query.fromJSON(this.className,this.queryParams)
+
+    let positionList = Object.keys(this.positionMap).filter(p=>this.positionMap[p]);
+    if(positionList?.length){
+      query.containedIn("position",positionList)
+    }
+
+    if(this.schema?.include){
+      query.include(...this.schema?.include)
+    }
+    query.notEqualTo("isDeleted",true)
+    query.addDescending("updatedAt")
+    query.skip((this.pageIndex - 1)* this.pageSize)
+    query.limit(this.pageSize)
+    if(isCount){
+      this.pageTotal = await query.count();
+      return
+    }
+    this.isLoading = true;
+    this.list = await query.find();
+    await this.fixPointerName()
+    this.isLoading = false;
+  }
+  async fixPointerName(){
+    let pointerColumns = this.fieldsArray?.filter(item=>item?.type=="Pointer")
+    this.list.forEach(item=>{
+      pointerColumns?.forEach(pitem=>{
+        let pointer = item.get(pitem?.key)
+        let id = pointer?.objectId || pointer?.id
+        if(id){
+          this.pointerMap[id] = pointer
+        }
+      })
+    })
+    await this.parseData.cachePointerList(Object.values(this.pointerMap))
+    Object.keys(this.pointerMap).forEach(async id=>{
+      let obj = this.parseData.ParseObjectCacheMap[id]
+      let field = this.fieldsArray?.find(item=>item?.className==obj?.className)
+      this.pointerShowMap[id] = await this.parseData.showNameByObj(obj,field?.showName)
+    })
+  }
+  pointerMap:any = {}
+  pointerShowMap:any = {}
+
+  /**
+   * 行内字段处理
+   */
+   toggleFiled(object:any,key:any){
+    object.set(key,!object.get(key))
+    object.save();
+   }
+}

+ 70 - 0
projects/textbook/src/app/comp-table/parse-data.service.ts

@@ -0,0 +1,70 @@
+import { Injectable } from '@angular/core';
+// import { TranslateService } from '@ngx-translate/core';
+import Parse from "parse";
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ParseDataService {
+
+  /**
+   * 数据源内存缓存
+   */
+  ParseObjectCacheMap:{
+    [key:string]:Parse.Object
+  } = {}
+  async cachePointerList(pointers:Array<Parse.Pointer|any>):Promise<any>{
+    let plist = pointers.map(pointer=>{
+      return new Promise(async (resolve)=>{
+        let id = pointer?.objectId || pointer?.id
+        let query = new Parse.Query(pointer?.className);
+        try{
+          this.ParseObjectCacheMap[id] = await query.get(id);
+        }catch(err){
+        }
+        resolve(true)
+      })
+    })
+    await Promise.all(plist);
+    console.log(this.ParseObjectCacheMap)
+    return true
+  }
+  async showNameByObj(obj:Parse.Object,showName:string="${name}"){
+    if(!obj?.id) return '无'
+    let text = showName;
+    let regex = /\${([^}]+)}/g;
+    let matches = [];
+    let match;
+    while ((match = regex.exec(text)) !== null) {
+      matches.push(match[1]);
+    }
+
+    // 获取模板标记数组
+    console.log(matches);
+    matches.forEach(keyword=>{
+      text = text.replaceAll("${"+keyword+"}",obj?.get(keyword)||"")
+    })
+    console.log(text)
+
+    // 特殊数据名称设定
+    // if(obj.className=="MinerCluster"){
+    //   let cname = obj?.get("parent")?.get("name") || ""
+    //   let uname = await this.getTrans( `${obj?.get("index")==1?'A单元':'B单元'}`);
+    //   text = cname + uname;
+    //   if(!obj?.get("index")){
+    //     return obj.get("cluster")?.get("name") || obj.get("name") // 非A/B单元,直接显示名称
+    //   }
+    // }
+    return text
+  }
+  constructor(
+    // private translate:TranslateService
+  ) { }
+  // getTrans(key:any){
+  //   return new Promise(resolve=>{
+  //     this.translate.get(key).subscribe(data=>{
+  //       resolve(data)
+  //     })
+  //   })
+  // }
+}

+ 3 - 1
projects/textbook/src/index.html

@@ -6,8 +6,10 @@
   <base href="/">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
+  <link href="https://fonts.loli.net/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
+  <link href="https://fonts.loli.net/icon?family=Material+Icons" rel="stylesheet">
 </head>
-<body>
+<body class="mat-typography">
   <app-root></app-root>
 </body>
 </html>

+ 12 - 0
projects/textbook/src/modules/nav-author/modules.routes.ts

@@ -1,6 +1,7 @@
 import { NgModule } from "@angular/core";
 import { RouterModule, Routes } from "@angular/router";
 import { PageHomeComponent } from './page-home/page-home.component';
+import { PageTextbookComponent } from "./page-textbook/page-textbook.component";
 const routes: Routes = [
   {
     path: 'home',
@@ -12,6 +13,17 @@ const routes: Routes = [
       //   pathMatch: "full",
       // },
     ]
+  },
+  {
+    path: 'textbook',
+    component: PageHomeComponent,
+    children:[
+      {
+        path: "list",
+        component:PageTextbookComponent
+        // loadComponent(import("./page-textbook/page-textbook.component").then(m=>m.PageTextbookComponent))
+      },
+    ]
   }
 ];
 @NgModule({

+ 2 - 2
projects/textbook/src/modules/nav-author/page-home/page-home.component.html

@@ -10,7 +10,7 @@
           <li
             nz-menu-item
             [nzSelected]="active == child.id"
-            (click)="toUrl('/nav-admin', child.id, { category: child.id })"
+            (click)="toUrl(child)"
           >
             {{ child.name }}
           </li>
@@ -20,7 +20,7 @@
       } @else {
       <li
         nz-menu-item
-        (click)="toUrl('/nav-admin', 'list')"
+        (click)="toUrl(item)"
         [nzSelected]="active == item.id"
       >
         <span nz-icon nzType="home" nzTheme="outline"></span>

+ 15 - 2
projects/textbook/src/modules/nav-author/page-home/page-home.component.ts

@@ -13,6 +13,17 @@ import { CommonCompModule } from '../../../services/common.modules'
 export class PageHomeComponent  implements OnInit {
 
   options:Array<any> = [
+    {
+      name:'教材管理',
+      id:'1',
+      child:[
+        {
+          name:'教材列表',
+          id:'1-1',
+          path:"/nav-author/textbook/list"
+        }
+      ]
+    },
     {
       name:'个人空间',
       id:'1',
@@ -32,9 +43,11 @@ export class PageHomeComponent  implements OnInit {
   ) { }
 
   ngOnInit() {}
-  toUrl(url:string, cateid:string, params?:any){
+  toUrl(child:any){
+    let cateid = child.id
     this.active = cateid
     localStorage.setItem('active', cateid)
-    // this.router.navigate([url, params ? params : {}])
+    console.log(child)
+    child?.path&&this.router.navigateByUrl(child?.path)
   }
 }

+ 1 - 0
projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.html

@@ -0,0 +1 @@
+<comp-table-list #list *ngIf="className && fieldsArray" [className]="className" [fieldsArray]="fieldsArray" [queryParams]="queryParams"></comp-table-list>

+ 0 - 0
projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.scss


+ 22 - 0
projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.spec.ts

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

+ 42 - 0
projects/textbook/src/modules/nav-author/page-textbook/page-textbook.component.ts

@@ -0,0 +1,42 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { ActivatedRoute, RouterOutlet } from '@angular/router';
+import { CompTableListComponent } from '../../../app/comp-table/comp-table-list/comp-table-list.component';
+import { EduTextbook } from '../../../schemas/EduTextbook';
+// import { TranslateService } from '@ngx-translate/core';
+import * as Parse from "parse";
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-page-textbook',
+  templateUrl: './page-textbook.component.html',
+  styleUrls: ['./page-textbook.component.scss'],
+  imports: [CommonModule,RouterOutlet,CompTableListComponent],
+  standalone: true,
+})
+export class PageTextbookComponent  implements OnInit {
+  @ViewChild(CompTableListComponent) list:CompTableListComponent|undefined
+
+  EduTextbook = EduTextbook
+  user:Parse.User|undefined
+  className:string|undefined
+  queryParams:any|undefined
+  fieldsArray:Array<any>|undefined
+
+  constructor(
+    private route: ActivatedRoute,
+    // private translate:TranslateService,
+  ) {
+    this.user = Parse.User.current();
+    this.className = this.EduTextbook.className
+    this.fieldsArray = this.EduTextbook.fieldsArray
+    this.queryParams = {where:{
+      // user:this.user?.toPointer(),
+      isDeleted:{$ne:true},
+    }}
+  }
+
+  ngOnInit(): void {
+    
+  }
+
+}

+ 64 - 0
projects/textbook/src/pipes/utilnow.pipe.ts

@@ -0,0 +1,64 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'utilnow',
+  standalone: true
+})
+export class UtilnowPipe implements PipeTransform {
+
+  transform(value: Date, args?: any): any {
+    let now = new Date();
+    let time = now.getTime() - value.getTime();
+    let seconds = time / 1000
+    if(seconds<=60){
+      return this.handleArgs(seconds.toFixed(0),"秒钟前",args)
+    }
+    let minutes = seconds / 60
+    if(minutes < 60){
+      return this.handleArgs(minutes.toFixed(0),"分钟前",args)
+    }
+    let hours = minutes / 60
+    if(hours < 24){
+      return this.handleArgs(hours.toFixed(0),"小时前",args)
+    }
+    let days = hours / 24
+    if(days < 7){
+      return this.handleArgs(days.toFixed(0),"天前",args)
+    }
+
+    let dateStr = `${value?.getFullYear()}-${value?.getMonth()+1}-${value?.getDate()}`
+    return dateStr
+  }
+
+     // 时间标记
+  enLocale:any = {
+      "秒钟前":" seconds ago",
+      "分钟前":" minutes ago",
+      "小时前":" hours ago",
+      "天前":" days ago",
+  }
+  handleArgs(value:any,unit:any,args:any):any{
+    if(args=="en"){
+      unit = this.enLocale[unit]
+    }
+    if(args?.constructor?.name == "TranslateService"){
+      console.log(unit)
+      if(args?.getDefaultLang()=="en"){
+        unit = this.enLocale[unit]
+      }
+      // unit = await new Promise(resolve=>{
+      //   args?.get(unit).subscribe(data=>{
+      // console.log(data)
+      // resolve(data)  
+      //   })
+      // })
+    }
+    if(args=="json"){
+      return {unit,value}
+    }else{
+      return value+unit
+    }
+
+  }
+
+}

+ 30 - 0
projects/textbook/src/schemas/EduTextbook.ts

@@ -0,0 +1,30 @@
+import { MatDialog } from "@angular/material/dialog";
+import Parse from "parse";
+
+export const EduTextbook = {
+    className:"EduTextbook",
+    include:["owner"],
+    buttons:[
+        // 行内操作
+        {
+            name:"绑定",
+            place:"item",
+            show:(options:{object:Parse.Object})=>{
+                if(options?.object?.get("type")=="unit") {return true} else return false
+            },
+            handle:(options:{dialog:MatDialog,object:Parse.Object,callback:any})=>{
+                // options?.clusterServ?.openMinerDialog(options?.dialog,options?.object)
+                console.log(options?.object.toJSON())
+                console.log(options)
+                if(options?.callback){
+                    options?.callback(options?.object)
+                }
+            }
+        },
+    ],
+    fieldsArray:[
+        {key:"title",name:"教材名称",type:"String",isHeader:true},
+        {key:"desc",name:"教材描述",type:"String"},
+        {key:"user",name:"创建人",type:"Pointer",className:"_User",isHeader:true,showName:"${name}"},
+    ]
+}

+ 3 - 1
projects/textbook/src/styles.scss

@@ -15,4 +15,6 @@
 //  @import "@ionic/angular/css/palettes/dark.always.css";
  /* @import "@ionic/angular/css/palettes/dark.class.css"; */
  /* @import "@ionic/angular/css/palettes/dark.system.css"; */
- 
+ 
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }

+ 5 - 19
server/db/schemas/edu-textbook.js

@@ -1,33 +1,19 @@
 const EduTextbook = {
     "className": "EduTextbook",
     "fields": {
-        "objectId": {
-            "type": "String"
-        },
-        "createdAt": {
-            "type": "Date"
-        },
-        "updatedAt": {
-            "type": "Date"
-        },
-        "ACL": {
-            "type": "ACL"
-        },
-        "mac": {
+        "title": {
             "type": "String",
             "required": false
         },
-        "mid": {
+        "desc": {
             "type": "String",
             "required": false
         },
-        "name": {
-            "type": "String",
+        "user": {
+            "type": "Pointer",
+            "targetClass":"_User",
             "required": false
         },
-        "subnets": {
-            "type": "Array"
-        },
         "isDeleted": {
             "type": "Boolean",
             "required": false

+ 6 - 2
server/package-lock.json

@@ -9,10 +9,12 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
-        "@embedded-postgres/linux-x64": "^16.2.0-beta.11",
-        "@embedded-postgres/windows-x64": "^16.2.0-beta.11",
         "parse-dashboard": "^5.4.0",
         "parse-server": "^7.0.0"
+      },
+      "devDependencies": {
+        "@embedded-postgres/linux-x64": "^16.2.0-beta.11",
+        "@embedded-postgres/windows-x64": "^16.2.0-beta.11"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -730,6 +732,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "hasInstallScript": true,
       "os": [
         "linux"
@@ -745,6 +748,7 @@
       "cpu": [
         "x64"
       ],
+      "dev": true,
       "hasInstallScript": true,
       "os": [
         "win32"

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů