angular - 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));
}
}
解决方案
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 。这没有您所追求的任何交互式功能,但它设置初始文件系统,设置当前根,然后设置查询。
推荐阅读
- windows - Windows 不会自动查找本地设备
- django - 重置密码时无法从主机帐户向用户电子邮件发送电子邮件
- codeigniter - 访问 public_html 上的文件时路由 CodeIgniter 中的问题
- javascript - 从反应组件的状态中删除密钥的最佳方法
- javascript - Ajax 不向烧瓶发送参数
- makefile - 使用 Makefile 构建目录 - 每次都编译
- python - 总结为什么keras自定义层会产生无意义的输出形状
- python - 如何使用 Spyder 调试 input() 函数调用?
- php - 当我使用 varchar 从数据库中获取数据并将其重新分配给数据库时出现问题
- ansible - 使用 ansible 将对象值与字典变量列表中的相同键合并