首页 > 解决方案 > Angular 应用程序中的文件管理器问题 - 文件和文件夹不可见

问题描述

我终于从教程中实现了文件管理器。我在 vscode 和 chrome 调试工具中没有任何错误,有人可以告诉我为什么我的文件夹不可见?

在此处输入图像描述

我不知道如何解决它。我会给你你需要的一切:)

更新 1

我认为问题在于从外部导入样式。我只创建了 index.html 和 style.scss - 这需要以某种方式连接吗?

在此处输入图像描述

索引.html:

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

样式.html:

@import '~@angular/material/prebuilt-themes/indigo-pink.css';

html {
  height: 100%;
}

body {
  margin: 0;
  height: 100%;
}

更新 2

我添加 mp-file-explorer.component 文件:

*.ts:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { MpFileElement } from '../../models/mp-file-element.model';
import { MatMenuTrigger } from '@angular/material';
import { MatDialog } from '@angular/material/dialog';
import { MpNewFolderDialogComponent } from '../messages/mp-new-folder-dialog/mp-new-folder-dialog.component';
import { MpRenameDialogComponent } from '../messages/mp-rename-dialog/mp-rename-dialog.component';

@Component({
  selector: 'lib-mp-file-explorer',
  templateUrl: './mp-file-explorer.component.html',
  styleUrls: ['./mp-file-explorer.component.scss']
})
export class MpFileExplorerComponent {

  @Input() fileElements: MpFileElement[] = [
    {
      id: '',
      isFolder: false,
      name: '',
      parent: ''
    }
  ];
  @Input() canNavigateUp = '';
  @Input() path = '';

  @Output() folderAdded = new EventEmitter<{ name: string}>();
  @Output() elementRemoved = new EventEmitter<MpFileElement>();
  @Output() elementRenamed = new EventEmitter<MpFileElement>();
  @Output() elementMoved = new EventEmitter<{
    element: MpFileElement,
    moveTo: MpFileElement
  }>();
  @Output() navigatedDown = new EventEmitter<MpFileElement>();
  @Output() navigatedUp = new EventEmitter();

  constructor(public dialog: MatDialog) {}

  deleteElement(element: MpFileElement) {
    this.elementRemoved.emit(element);
  }

  navigate(element: MpFileElement) {
    if (element.isFolder) {
      this.navigatedDown.emit();
    }
  }

  navigateUp() {
    this.navigatedUp.emit();
  }

  moveElement(element: MpFileElement, moveTo: MpFileElement) {
    this.elementMoved.emit({
      element,
      moveTo
    });
  }

  openNewFolderDialog() {
    const dialogRef = this.dialog.open(MpNewFolderDialogComponent);
    dialogRef.afterClosed().subscribe(res => {
      if (res) {
        this.folderAdded.emit({ name: res });
      }
    });
  }

  openRenameDialog(element: MpFileElement) {
    const dialogRef = this.dialog.open(MpRenameDialogComponent);
    dialogRef.afterClosed().subscribe(res => {
      if (res) {
        element.name = res;
        this.elementRenamed.emit(element);
      }
    });
  }

  openMenu(event: MouseEvent, element: MpFileElement, viewChild: MatMenuTrigger) {
    event.preventDefault();
    viewChild.openMenu();
  }
}

*.html:

<mat-toolbar>
  <mat-icon *ngIf="canNavigateUp" class="pointer" (click)="navigateUp()">
    arrow_back
  </mat-icon>
  <span style="margin-left: 8px"> {{path || 'Files'}} </span>
  <span class="spacer"></span>
  <mat-icon class="pointer" (click)="openNewFolderDialog()">
    create_new_folder
  </mat-icon>
</mat-toolbar>

<div
  class="container"
  fxFlex
  fxLayout="row"
  fxLayoutAlign="space-between stretch"
>
    <div class="content" fxFlex fxLayout="row">
      <mat-grid-list cols="8" rowHeight="100px" fxFlex>
        <mat-grid-tile
          *ngFor="let element of fileElements"
          class="file-or-folder"
        >
          <span
            [matMenuTriggerFor]="rootMenu"
            [matMenuTriggerData]="{element: element}"
            #menuTrigger="matMenuTrigger"
          >
          </span>
          <div
            fxLayout="column"
            fxLayoutAlign="space-between center"
            (click)="navigate(element)"
            (contextmenu)="openMenu($event, menuTrigger)"
          >
            <mat-icon
              color="primary"
              class="file-or-folder-icon pointer"
              *ngIf="element.isFolder"
            >
              folder
            </mat-icon>
            <mat-icon
              color="primary"
              class="file-or-folder-icon pointer"
              *ngIf="!element.isFolder"
            >
              insert_drive_file
            </mat-icon>

            <span>{{element.name}}</span>
          </div>
        </mat-grid-tile>
      </mat-grid-list>
    </div>
  </div>

  <mat-menu #rootMenu="matMenu" [overlapTrigger]="false">
    <ng-template matMenuContent let-element="element">
      <button
        mat-menu-item
        [matMenuTriggerFor]="moveToMenu"
        [matMenuTriggerData]="{self: element}"
      >
        <mat-icon>open_with</mat-icon>
        <span>Move To</span>
      </button>
      <button mat-menu-item (click)="openRenameDialog(element)">
        <mat-icon>edit</mat-icon>
        <span>Rename</span>
      </button>
      <button mat-menu-item (click)="deleteElement(element)">
        <mat-icon>delete</mat-icon>
        <span>Delete</span>
      </button>
    </ng-template>
  </mat-menu>

  <mat-menu #moveToMenu="matMenu">
    <ng-template matMenuContent let-self="self">
      <ng-container *ngFor="let element of fileElements">
        <button
          *ngIf="element.isFolder && element.id !== self.id"
          mat-menu-item
          (click)="moveElement(self, element)"
        >
          {{element.name}}
        </button>
      </ng-container>
    </ng-template>
  </mat-menu>

*.scss:

:host {
  height: 50%;
  width: 50%;
  display: flex;
  flex-direction: column;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  margin-left: 25%;
  margin-top: 25%;
}

.file-or-folder {
  padding: 8px;
  overflow: hidden;
}

.file-or-folder-icon {
  width: 50px;
  height: 50px;
  font-size: 50px;
}

.pointer {
  cursor: pointer;
}

.spacer {
  flex: 1 1 auto;
}

更新 3

我添加了更多细节:

main.component.ts

import { MatSidenav, MatGridTileHeaderCssMatStyler } from '@angular/material';
import { Component, ViewChild, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CoreService, CoreHelper, Indicator } from 'mpcore';
import { Subject, Observable } from 'rxjs';
import { takeWhile, takeUntil } from 'rxjs/operators';
import { MpFileElement } from 'projects/mpcore-app/lib-common/src/lib/mp-file-explorer/models/mp-file-element.model';
import { MpFileService } from 'projects/mpcore-app/lib-common/src/lib/mp-file-explorer/services/file.service';

@Component({
  // tslint:disable-next-line:component-selector
  selector: 'main-root',
  templateUrl: './main.component.html',
  encapsulation: ViewEncapsulation.None
})
export class MainComponent implements OnDestroy, OnInit {
  public _onDestroy = new Subject<void>();
  protected isInitialized = false;

  public applicationName: string;
  public selectedItems: number;
  public took: number | null = null;
  public customText: string | null = null;
  public allItems: number;
  public openCloseMenu = 'closeMenu';
  public MainIndicator: Indicator = new Indicator();

  public fileElements: Observable<MpFileElement[]> = new Observable<MpFileElement[]>();

  currentRoot!: MpFileElement;
  currentPath!: string;
  canNavigateUp!: boolean;

  @ViewChild('sidenav', { static: false }) sidenav: MatSidenav | null = null;
  public constructor(

    private coreService: CoreService,
    private translate: TranslateService,
    private fileService: MpFileService
    ) {
    this.canNavigateUp = false;
    this.isInitialized = true;
    this.coreService.applicationName = this.applicationName = 'file-manager';
    this.allItems = 0;
    this.selectedItems = 0;
    this.coreService.langChanged$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe(() => {
        this.translate.use(this.coreService.lang);
      });

    this.coreService.showUserPanel$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe((data) => {
        if (data) {
          (this.sidenav as MatSidenav).open();
        } else {
          (this.sidenav as MatSidenav).close();
        }
      });

    this.coreService.displayAllItems$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe((data) => {
        this.allItems = data;
      });

    this.coreService.displaySelectedItems$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe((data) => {
        this.selectedItems = data;
      });

    this.coreService.displayTook$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe((data) => {
        this.took = data;
      });

    this.coreService.displayCustomText$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe((data) => {
        this.customText = data;
      });

    this.coreService.showModulePanel$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe(() => {
        if (this.openCloseMenu === 'openMenu') {
          this.openCloseMenu = 'closeMenu';
        } else {
          this.openCloseMenu = 'openMenu';
        }
      });

    this.coreService.showIndicator$
      .pipe(
        takeWhile(() => this.isInitialized),
        takeUntil(this._onDestroy)
      )
      .subscribe((message) => {
        if (CoreHelper.hasValue(message)) {
          this.MainIndicator.ShowBusy(message);
        } else {
          this.MainIndicator.HideBusy();
        }
      });
  }

  public ngOnDestroy(): void {
    this.isInitialized = false;
    this._onDestroy.next();
    this._onDestroy.complete();
  }

  public openCoreModuleList() {
    if (this.openCloseMenu === 'openMenu') {
      this.openCloseMenu = 'closeMenu';
    } else {
      this.openCloseMenu = 'openMenu';
    }
  }

  ngOnInit() {
    const folderA = this.fileService.add({ name: 'Folder A', isFolder: true, parent: 'root' });

    if ( folderA.id !== undefined ) {
      this.fileService.add({ name: 'Folder B', isFolder: true, parent: 'root' });
      this.fileService.add({ name: 'Folder C', isFolder: true, parent: folderA.id });
      this.fileService.add({ name: 'File A', isFolder: false, parent: 'root' });
      this.fileService.add({ name: 'File B', isFolder: false, parent: 'root' });
    }

    this.updateFileElementQuery();
  }

  addFolder(folder: { name: string }) {
    if ( this.currentRoot !== undefined && this.currentRoot.id ) {
      this.fileService.add({ isFolder: true, name: folder.name, parent: this.currentRoot ? this.currentRoot.id : 'root' });
    }

    this.updateFileElementQuery();
  }

  removeElement(element: MpFileElement) {
    if ( element.id !== undefined ) {
      this.fileService.delete(element.id);
    }
    this.updateFileElementQuery();
  }

  moveElement(event: { element: MpFileElement; moveTo: MpFileElement }) {
    if ( event.element.id !== undefined ) {
      this.fileService.update(event.element.id, { parent: event.moveTo.id });
    }
    this.updateFileElementQuery();
  }

  renameElement(element: MpFileElement) {
    if ( element.id !== undefined ) {
      this.fileService.update(element.id, { name: element.name });
    }
    this.updateFileElementQuery();
  }

  updateFileElementQuery() {
    if ( this.currentRoot !== undefined && this.currentRoot.id ) {
      this.fileElements = this.fileService.queryInFolder(this.currentRoot ? this.currentRoot.id : 'root');
    }
  }

  navigateUp() {
    if (this.currentRoot && this.currentRoot.parent === 'root') {
      this.currentRoot.name = 'root';
      this.canNavigateUp = false;
      this.updateFileElementQuery();
    } else {
      if ( this.currentRoot.parent !== undefined ) {
        const currentRootTemp = this.fileService.get(this.currentRoot.parent);
        if (currentRootTemp !== undefined) {
          this.currentRoot = currentRootTemp;
        }
      }
      this.updateFileElementQuery();
    }
    this.currentPath = this.popFromPath(this.currentPath);
  }

  navigateToFolder(element: MpFileElement) {
    this.currentRoot = element;
    this.updateFileElementQuery();
    if ( element.name !== undefined ) {
      this.currentPath = this.pushToPath(this.currentPath, element.name);
    }
    this.canNavigateUp = true;
  }

  pushToPath(path: string, folderName: string) {
    let p = path ? path : '';
    p += `${folderName}/`;
    return p;
  }

  popFromPath(path: string) {
    let p = path ? path : '';
    const split = p.split('/');
    split.splice(split.length - 2, 1);
    p = split.join('/');
    return p;
  }
}

Main.component.html:

<div class="full-height fxcol" [ngClass]="'side-menu-disabled'">
  <core-top-panel [applicationName]="applicationName"></core-top-panel>
  <div class="fxgrow">
    <div class="core-main-window fxgrow fxcol">
      <mat-sidenav-container class="fxfill">
        <mat-sidenav #sidenav position="end" mode="over">
          <core-user-panel></core-user-panel>
        </mat-sidenav>
        <mp-indicator [indicator]="MainIndicator"></mp-indicator>

        <div class="full-height fxcol">
          <div style="padding: 100px; height: 100%; box-sizing: border-box;">
            <mat-card style="height: 100%; box-sizing: border-box; padding:0">
              <lib-mp-file-explorer [fileElements]="fileElements | async" [path]="currentPath" [canNavigateUp]="canNavigateUp" (folderAdded)="addFolder($event)"
                (elementRemoved)="removeElement($event)" (navigatedDown)="navigateToFolder($event)" (navigatedUp)="navigateUp()" (elementRenamed)="renameElement($event)"
                (elementMoved)="moveElement($event)">
              </lib-mp-file-explorer>
            </mat-card>
          </div>
        </div>
      </mat-sidenav-container>
      <core-footer [allItems]="allItems" [selectedItems]="selectedItems" [took]="took" [customText]="customText"> </core-footer>
    </div>
  </div>
</div>

更新 4

import { Injectable } from '@angular/core';

import { v4 } from 'uuid';
import { MpFileElement } from '../models/mp-file-element.model';
import { Observable } from 'rxjs/internal/Observable';
import { BehaviorSubject } from 'rxjs';
import { NullTemplateVisitor } from '@angular/compiler';

export interface IFileService {
    add(fileElement: MpFileElement): MpFileElement;
    delete(id: string): void;
    update(id: string, update: Partial<MpFileElement>): void;
    queryInFolder(folderId: string): Observable<MpFileElement[]>;
    get(id: string): MpFileElement | void;
}

@Injectable()
export class MpFileService implements IFileService {

    constructor() {}
    private map = new Map<string, MpFileElement>()

    private querySubject: BehaviorSubject<MpFileElement[]> = new BehaviorSubject<MpFileElement[]>([]);

    add(fileElement: MpFileElement): MpFileElement {
        fileElement.id = v4();
        this.map.set(fileElement.id, this.clone(fileElement));
        return fileElement;
    }

    delete(id: string): void {
        this.map.delete(id);
    }

    update(id: string, update: Partial<MpFileElement>): void {
        let element = this.map.get(id);
        element = Object.assign(element, update);
        if ( element.id !== undefined ) {
            this.map.set(element.id, element);
        }
    }
    queryInFolder(folderId: string): Observable<MpFileElement[]> {
        const result: MpFileElement[] = [];
        this.map.forEach(element => {
            if (element.parent === folderId) {
                result.push(this.clone(element));
            }
        });
        if (!this.querySubject) {
            this.querySubject = new BehaviorSubject(result);
        } else {
            this.querySubject.next(result);
        }
        return this.querySubject.asObservable();
    }

    get(id: string): MpFileElement | undefined {
        if ( id !== undefined ) {
            return this.map.get(id);
        }
        return undefined;
    }

    clone(element: MpFileElement): MpFileElement {
        return JSON.parse(JSON.stringify(element));
    }
}

标签: angulartypescript

解决方案


html 中的文件列表绑定到

@Input() fileElements: MpFileElement[] = [
    {
      id: '',
      isFolder: false,
      name: '',
      parent: ''
    }
  ];

这是一个@Input()属性,这意味着它可以由父组件设置。有点奇怪的是它也被分配给一个几乎空的数组。

您还没有发布父组件,所以很难看出为什么 @fileElements 没有内容。

编辑:

好的,现在您已经发布了您的父组件,很容易看出您遇到问题的原因。

首先,我会将构造函数主体中的所有代码移动到ngOnInit(). 在组件中,构造函数实际上应该只用于注入您的依赖项。ngOnInit()是应该进行大量初始化的地方。

其次,fileElements 仅设置在updateFileElementsQuery. this.currentRoot在调用此方法之前我看不到设置的位置,因此this.fileElements由于保护子句中的错误语句而不会设置:

updateFileElementQuery() {
    if ( this.currentRoot !== undefined && this.currentRoot.id ) {
      this.fileElements = this.fileService.queryInFolder(this.currentRoot ? this.currentRoot.id : 'root');
    }
  }

尝试在此方法的开头放置一个 console.log(this.currentRoot) 以查看是否是这种情况。

我在这里创建了一个非常简单的 stackblitz 来演示事件的一般顺序:https ://stackblitz.com/edit/angular-m5nmru 。这没有您所追求的任何交互式功能,但它设置初始文件系统,设置当前根,然后设置查询。


推荐阅读