hedwi-app-menu 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,13 +10,18 @@ export class AppMenuComponent {
10
10
  this.cacheTs = '';
11
11
  /** 应用列表,不传则使用默认列表 */
12
12
  this.apps = HEDWI_APP_MENU_DEFAULT_APPS;
13
+ /** 按 groupIndex 分组后的列表(仅当 apps 变化时重新计算,避免每次变更检测返回新数组导致图片循环请求) */
14
+ this.appGroups = [];
13
15
  }
14
- /** 返回应用显示名称 */
15
- displayName(item) {
16
- return item.name;
16
+ ngOnInit() {
17
+ this.appGroups = this.buildAppGroups();
17
18
  }
18
- /** 按 groupIndex 分组,用于按行渲染 */
19
- get appGroups() {
19
+ ngOnChanges(changes) {
20
+ if (changes['apps']) {
21
+ this.appGroups = this.buildAppGroups();
22
+ }
23
+ }
24
+ buildAppGroups() {
20
25
  const map = new Map();
21
26
  for (const item of this.apps) {
22
27
  const g = item.groupIndex ?? (item.extra ? 1 : 0);
@@ -27,9 +32,21 @@ export class AppMenuComponent {
27
32
  const maxG = Math.max(0, ...map.keys());
28
33
  return Array.from({ length: maxG + 1 }, (_, i) => map.get(i) ?? []);
29
34
  }
35
+ /** trackBy:按分组索引,避免无意义的 DOM 重建 */
36
+ trackByGroupIndex(index, _group) {
37
+ return index;
38
+ }
39
+ /** trackBy:按 path 唯一标识应用项,避免无意义的 DOM 重建与图片重复请求 */
40
+ trackByItemPath(index, item) {
41
+ return item.path;
42
+ }
43
+ /** 返回应用显示名称 */
44
+ displayName(item) {
45
+ return item.name;
46
+ }
30
47
  iconUrl(item) {
31
48
  const base = (this.baseUrl || '').replace(/\/$/, '');
32
- const path = `${base}/static/mail/assets/images/${item.icon}`;
49
+ const path = `${base}/mail/assets/images/${item.icon}`;
33
50
  return this.cacheTs ? `${path}?v=${this.cacheTs}` : path;
34
51
  }
35
52
  linkHref(item) {
@@ -39,11 +56,11 @@ export class AppMenuComponent {
39
56
  return this.cacheTs ? `${fullPath}${sep}v=${this.cacheTs}` : fullPath;
40
57
  }
41
58
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AppMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
42
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AppMenuComponent, selector: "hedwi-app-menu", inputs: { baseUrl: "baseUrl", cacheTs: "cacheTs", apps: "apps" }, ngImport: i0, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); }
59
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AppMenuComponent, selector: "hedwi-app-menu", inputs: { baseUrl: "baseUrl", cacheTs: "cacheTs", apps: "apps" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups; trackBy: trackByGroupIndex\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group; trackBy: trackByItemPath\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); }
43
60
  }
44
61
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AppMenuComponent, decorators: [{
45
62
  type: Component,
46
- args: [{ selector: 'hedwi-app-menu', standalone: false, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"] }]
63
+ args: [{ selector: 'hedwi-app-menu', standalone: false, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups; trackBy: trackByGroupIndex\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group; trackBy: trackByItemPath\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"] }]
47
64
  }], propDecorators: { baseUrl: [{
48
65
  type: Input
49
66
  }], cacheTs: [{
@@ -51,4 +68,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
51
68
  }], apps: [{
52
69
  type: Input
53
70
  }] } });
54
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLW1lbnUuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvaGVkd2ktYXBwLW1lbnUvc3JjL2xpYi9hcHAtbWVudS5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9wcm9qZWN0cy9oZWR3aS1hcHAtbWVudS9zcmMvbGliL2FwcC1tZW51LmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2pELE9BQU8sRUFBZ0IsMkJBQTJCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQzs7O0FBUTdFLE1BQU0sT0FBTyxnQkFBZ0I7SUFON0I7UUFPRSx1Q0FBdUM7UUFDOUIsWUFBTyxHQUFHLEVBQUUsQ0FBQztRQUN0Qix1QkFBdUI7UUFDZCxZQUFPLEdBQUcsRUFBRSxDQUFDO1FBQ3RCLHFCQUFxQjtRQUNaLFNBQUksR0FBbUIsMkJBQTJCLENBQUM7S0ErQjdEO0lBN0JDLGVBQWU7SUFDZixXQUFXLENBQUMsSUFBa0I7UUFDNUIsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRCw2QkFBNkI7SUFDN0IsSUFBSSxTQUFTO1FBQ1gsTUFBTSxHQUFHLEdBQUcsSUFBSSxHQUFHLEVBQTBCLENBQUM7UUFDOUMsS0FBSyxNQUFNLElBQUksSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFO1lBQzVCLE1BQU0sQ0FBQyxHQUFHLElBQUksQ0FBQyxVQUFVLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ2xELElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztZQUNoQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBRSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtRQUNELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLEdBQUcsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFDeEMsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxFQUFFLElBQUksR0FBRyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7SUFDdEUsQ0FBQztJQUVELE9BQU8sQ0FBQyxJQUFrQjtRQUN4QixNQUFNLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNyRCxNQUFNLElBQUksR0FBRyxHQUFHLElBQUksOEJBQThCLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUM5RCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxNQUFNLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0lBQzNELENBQUM7SUFFRCxRQUFRLENBQUMsSUFBa0I7UUFDekIsTUFBTSxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDckQsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7UUFDMUQsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7UUFDL0MsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxHQUFHLFFBQVEsR0FBRyxHQUFHLEtBQUssSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUM7SUFDeEUsQ0FBQzsrR0FwQ1UsZ0JBQWdCO21HQUFoQixnQkFBZ0Isd0hDVDdCLHllQWdCQTs7NEZEUGEsZ0JBQWdCO2tCQU41QixTQUFTOytCQUNFLGdCQUFnQixjQUNkLEtBQUs7OEJBTVIsT0FBTztzQkFBZixLQUFLO2dCQUVHLE9BQU87c0JBQWYsS0FBSztnQkFFRyxJQUFJO3NCQUFaLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBIZWR3aUFwcEl0ZW0sIEhFRFdJX0FQUF9NRU5VX0RFRkFVTFRfQVBQUyB9IGZyb20gJy4vYXBwLW1lbnUudHlwZXMnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdoZWR3aS1hcHAtbWVudScsXG4gIHN0YW5kYWxvbmU6IGZhbHNlLFxuICB0ZW1wbGF0ZVVybDogJy4vYXBwLW1lbnUuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9hcHAtbWVudS5jb21wb25lbnQuY3NzJ10sXG59KVxuZXhwb3J0IGNsYXNzIEFwcE1lbnVDb21wb25lbnQge1xuICAvKiog6Z2Z5oCB6LWE5rqQ5Z+656GAIFVSTO+8jOWmgiBlbnZpcm9ubWVudC5iYXNldXJsICovXG4gIEBJbnB1dCgpIGJhc2VVcmwgPSAnJztcbiAgLyoqIOe8k+WtmOaXtumXtOaIs++8jOeUqOS6jiBVUkwg6Ziy57yT5a2YICovXG4gIEBJbnB1dCgpIGNhY2hlVHMgPSAnJztcbiAgLyoqIOW6lOeUqOWIl+ihqO+8jOS4jeS8oOWImeS9v+eUqOm7mOiupOWIl+ihqCAqL1xuICBASW5wdXQoKSBhcHBzOiBIZWR3aUFwcEl0ZW1bXSA9IEhFRFdJX0FQUF9NRU5VX0RFRkFVTFRfQVBQUztcblxuICAvKiog6L+U5Zue5bqU55So5pi+56S65ZCN56ewICovXG4gIGRpc3BsYXlOYW1lKGl0ZW06IEhlZHdpQXBwSXRlbSk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGl0ZW0ubmFtZTtcbiAgfVxuXG4gIC8qKiDmjIkgZ3JvdXBJbmRleCDliIbnu4TvvIznlKjkuo7mjInooYzmuLLmn5MgKi9cbiAgZ2V0IGFwcEdyb3VwcygpOiBIZWR3aUFwcEl0ZW1bXVtdIHtcbiAgICBjb25zdCBtYXAgPSBuZXcgTWFwPG51bWJlciwgSGVkd2lBcHBJdGVtW10+KCk7XG4gICAgZm9yIChjb25zdCBpdGVtIG9mIHRoaXMuYXBwcykge1xuICAgICAgY29uc3QgZyA9IGl0ZW0uZ3JvdXBJbmRleCA/PyAoaXRlbS5leHRyYSA/IDEgOiAwKTtcbiAgICAgIGlmICghbWFwLmhhcyhnKSkgbWFwLnNldChnLCBbXSk7XG4gICAgICBtYXAuZ2V0KGcpIS5wdXNoKGl0ZW0pO1xuICAgIH1cbiAgICBjb25zdCBtYXhHID0gTWF0aC5tYXgoMCwgLi4ubWFwLmtleXMoKSk7XG4gICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IG1heEcgKyAxIH0sIChfLCBpKSA9PiBtYXAuZ2V0KGkpID8/IFtdKTtcbiAgfVxuXG4gIGljb25VcmwoaXRlbTogSGVkd2lBcHBJdGVtKTogc3RyaW5nIHtcbiAgICBjb25zdCBiYXNlID0gKHRoaXMuYmFzZVVybCB8fCAnJykucmVwbGFjZSgvXFwvJC8sICcnKTtcbiAgICBjb25zdCBwYXRoID0gYCR7YmFzZX0vc3RhdGljL21haWwvYXNzZXRzL2ltYWdlcy8ke2l0ZW0uaWNvbn1gO1xuICAgIHJldHVybiB0aGlzLmNhY2hlVHMgPyBgJHtwYXRofT92PSR7dGhpcy5jYWNoZVRzfWAgOiBwYXRoO1xuICB9XG5cbiAgbGlua0hyZWYoaXRlbTogSGVkd2lBcHBJdGVtKTogc3RyaW5nIHtcbiAgICBjb25zdCBiYXNlID0gKHRoaXMuYmFzZVVybCB8fCAnJykucmVwbGFjZSgvXFwvJC8sICcnKTtcbiAgICBjb25zdCBmdWxsUGF0aCA9IGJhc2UgPyBgJHtiYXNlfSR7aXRlbS5wYXRofWAgOiBpdGVtLnBhdGg7XG4gICAgY29uc3Qgc2VwID0gZnVsbFBhdGguaW5jbHVkZXMoJz8nKSA/ICcmJyA6ICc/JztcbiAgICByZXR1cm4gdGhpcy5jYWNoZVRzID8gYCR7ZnVsbFBhdGh9JHtzZXB9dj0ke3RoaXMuY2FjaGVUc31gIDogZnVsbFBhdGg7XG4gIH1cbn1cbiIsIjxkaXYgY2xhc3M9XCJoZWR3aS1hcHAtbGlzdFwiICpuZ0Zvcj1cImxldCBncm91cCBvZiBhcHBHcm91cHNcIj5cbiAgPGRpdlxuICAgIGNsYXNzPVwiaGVkd2ktYXBwLWl0ZW1cIlxuICAgIFtjbGFzcy5leHRyYV09XCJncm91cFswXT8uZXh0cmFcIlxuICAgICpuZ0Zvcj1cImxldCBpdGVtIG9mIGdyb3VwXCJcbiAgPlxuICAgIDxhIFthdHRyLmhyZWZdPVwibGlua0hyZWYoaXRlbSlcIiB0YXJnZXQ9XCJfYmxhbmtcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJoZWR3aS1hcHAtbG9nb1wiPlxuICAgICAgICA8aW1nIFtzcmNdPVwiaWNvblVybChpdGVtKVwiIFthbHRdPVwiZGlzcGxheU5hbWUoaXRlbSlcIiAvPlxuICAgICAgPC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiaGVkd2ktYXBwLW5hbWVcIj5cbiAgICAgICAgPHNwYW4+e3sgZGlzcGxheU5hbWUoaXRlbSkgfX08L3NwYW4+XG4gICAgICA8L2Rpdj5cbiAgICA8L2E+XG4gIDwvZGl2PlxuPC9kaXY+XG4iXX0=
71
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLW1lbnUuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvaGVkd2ktYXBwLW1lbnUvc3JjL2xpYi9hcHAtbWVudS5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9wcm9qZWN0cy9oZWR3aS1hcHAtbWVudS9zcmMvbGliL2FwcC1tZW51LmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFvQyxNQUFNLGVBQWUsQ0FBQztBQUNuRixPQUFPLEVBQWdCLDJCQUEyQixFQUFFLE1BQU0sa0JBQWtCLENBQUM7OztBQVE3RSxNQUFNLE9BQU8sZ0JBQWdCO0lBTjdCO1FBT0UsdUNBQXVDO1FBQzlCLFlBQU8sR0FBRyxFQUFFLENBQUM7UUFDdEIsdUJBQXVCO1FBQ2QsWUFBTyxHQUFHLEVBQUUsQ0FBQztRQUN0QixxQkFBcUI7UUFDWixTQUFJLEdBQW1CLDJCQUEyQixDQUFDO1FBRTVELGlFQUFpRTtRQUNqRSxjQUFTLEdBQXFCLEVBQUUsQ0FBQztLQWtEbEM7SUFoREMsUUFBUTtRQUNOLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3pDLENBQUM7SUFFRCxXQUFXLENBQUMsT0FBc0I7UUFDaEMsSUFBSSxPQUFPLENBQUMsTUFBTSxDQUFDLEVBQUU7WUFDbkIsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7U0FDeEM7SUFDSCxDQUFDO0lBRU8sY0FBYztRQUNwQixNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsRUFBMEIsQ0FBQztRQUM5QyxLQUFLLE1BQU0sSUFBSSxJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUU7WUFDNUIsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLFVBQVUsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQ2hDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ3hCO1FBQ0QsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN4QyxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsSUFBSSxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN0RSxDQUFDO0lBRUQsa0NBQWtDO0lBQ2xDLGlCQUFpQixDQUFDLEtBQWEsRUFBRSxNQUFzQjtRQUNyRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxrREFBa0Q7SUFDbEQsZUFBZSxDQUFDLEtBQWEsRUFBRSxJQUFrQjtRQUMvQyxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUM7SUFDbkIsQ0FBQztJQUVELGVBQWU7SUFDZixXQUFXLENBQUMsSUFBa0I7UUFDNUIsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRCxPQUFPLENBQUMsSUFBa0I7UUFDeEIsTUFBTSxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDckQsTUFBTSxJQUFJLEdBQUcsR0FBRyxJQUFJLHVCQUF1QixJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDdkQsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksTUFBTSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUMzRCxDQUFDO0lBRUQsUUFBUSxDQUFDLElBQWtCO1FBQ3pCLE1BQU0sSUFBSSxHQUFHLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ3JELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1FBQzFELE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO1FBQy9DLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsR0FBRyxRQUFRLEdBQUcsR0FBRyxLQUFLLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDO0lBQ3hFLENBQUM7K0dBMURVLGdCQUFnQjttR0FBaEIsZ0JBQWdCLDZJQ1Q3QiwraEJBZ0JBOzs0RkRQYSxnQkFBZ0I7a0JBTjVCLFNBQVM7K0JBQ0UsZ0JBQWdCLGNBQ2QsS0FBSzs4QkFNUixPQUFPO3NCQUFmLEtBQUs7Z0JBRUcsT0FBTztzQkFBZixLQUFLO2dCQUVHLElBQUk7c0JBQVosS0FBSyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgSW5wdXQsIE9uQ2hhbmdlcywgT25Jbml0LCBTaW1wbGVDaGFuZ2VzIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBIZWR3aUFwcEl0ZW0sIEhFRFdJX0FQUF9NRU5VX0RFRkFVTFRfQVBQUyB9IGZyb20gJy4vYXBwLW1lbnUudHlwZXMnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdoZWR3aS1hcHAtbWVudScsXG4gIHN0YW5kYWxvbmU6IGZhbHNlLFxuICB0ZW1wbGF0ZVVybDogJy4vYXBwLW1lbnUuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9hcHAtbWVudS5jb21wb25lbnQuY3NzJ10sXG59KVxuZXhwb3J0IGNsYXNzIEFwcE1lbnVDb21wb25lbnQgaW1wbGVtZW50cyBPbkNoYW5nZXMsIE9uSW5pdCB7XG4gIC8qKiDpnZnmgIHotYTmupDln7rnoYAgVVJM77yM5aaCIGVudmlyb25tZW50LmJhc2V1cmwgKi9cbiAgQElucHV0KCkgYmFzZVVybCA9ICcnO1xuICAvKiog57yT5a2Y5pe26Ze05oiz77yM55So5LqOIFVSTCDpmLLnvJPlrZggKi9cbiAgQElucHV0KCkgY2FjaGVUcyA9ICcnO1xuICAvKiog5bqU55So5YiX6KGo77yM5LiN5Lyg5YiZ5L2/55So6buY6K6k5YiX6KGoICovXG4gIEBJbnB1dCgpIGFwcHM6IEhlZHdpQXBwSXRlbVtdID0gSEVEV0lfQVBQX01FTlVfREVGQVVMVF9BUFBTO1xuXG4gIC8qKiDmjIkgZ3JvdXBJbmRleCDliIbnu4TlkI7nmoTliJfooajvvIjku4XlvZMgYXBwcyDlj5jljJbml7bph43mlrDorqHnrpfvvIzpgb/lhY3mr4/mrKHlj5jmm7Tmo4DmtYvov5Tlm57mlrDmlbDnu4Tlr7zoh7Tlm77niYflvqrnjq/or7fmsYLvvIkgKi9cbiAgYXBwR3JvdXBzOiBIZWR3aUFwcEl0ZW1bXVtdID0gW107XG5cbiAgbmdPbkluaXQoKTogdm9pZCB7XG4gICAgdGhpcy5hcHBHcm91cHMgPSB0aGlzLmJ1aWxkQXBwR3JvdXBzKCk7XG4gIH1cblxuICBuZ09uQ2hhbmdlcyhjaGFuZ2VzOiBTaW1wbGVDaGFuZ2VzKTogdm9pZCB7XG4gICAgaWYgKGNoYW5nZXNbJ2FwcHMnXSkge1xuICAgICAgdGhpcy5hcHBHcm91cHMgPSB0aGlzLmJ1aWxkQXBwR3JvdXBzKCk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBidWlsZEFwcEdyb3VwcygpOiBIZWR3aUFwcEl0ZW1bXVtdIHtcbiAgICBjb25zdCBtYXAgPSBuZXcgTWFwPG51bWJlciwgSGVkd2lBcHBJdGVtW10+KCk7XG4gICAgZm9yIChjb25zdCBpdGVtIG9mIHRoaXMuYXBwcykge1xuICAgICAgY29uc3QgZyA9IGl0ZW0uZ3JvdXBJbmRleCA/PyAoaXRlbS5leHRyYSA/IDEgOiAwKTtcbiAgICAgIGlmICghbWFwLmhhcyhnKSkgbWFwLnNldChnLCBbXSk7XG4gICAgICBtYXAuZ2V0KGcpIS5wdXNoKGl0ZW0pO1xuICAgIH1cbiAgICBjb25zdCBtYXhHID0gTWF0aC5tYXgoMCwgLi4ubWFwLmtleXMoKSk7XG4gICAgcmV0dXJuIEFycmF5LmZyb20oeyBsZW5ndGg6IG1heEcgKyAxIH0sIChfLCBpKSA9PiBtYXAuZ2V0KGkpID8/IFtdKTtcbiAgfVxuXG4gIC8qKiB0cmFja0J577ya5oyJ5YiG57uE57Si5byV77yM6YG/5YWN5peg5oSP5LmJ55qEIERPTSDph43lu7ogKi9cbiAgdHJhY2tCeUdyb3VwSW5kZXgoaW5kZXg6IG51bWJlciwgX2dyb3VwOiBIZWR3aUFwcEl0ZW1bXSk6IG51bWJlciB7XG4gICAgcmV0dXJuIGluZGV4O1xuICB9XG5cbiAgLyoqIHRyYWNrQnnvvJrmjIkgcGF0aCDllK/kuIDmoIfor4blupTnlKjpobnvvIzpgb/lhY3ml6DmhI/kuYnnmoQgRE9NIOmHjeW7uuS4juWbvueJh+mHjeWkjeivt+axgiAqL1xuICB0cmFja0J5SXRlbVBhdGgoaW5kZXg6IG51bWJlciwgaXRlbTogSGVkd2lBcHBJdGVtKTogc3RyaW5nIHtcbiAgICByZXR1cm4gaXRlbS5wYXRoO1xuICB9XG5cbiAgLyoqIOi/lOWbnuW6lOeUqOaYvuekuuWQjeensCAqL1xuICBkaXNwbGF5TmFtZShpdGVtOiBIZWR3aUFwcEl0ZW0pOiBzdHJpbmcge1xuICAgIHJldHVybiBpdGVtLm5hbWU7XG4gIH1cblxuICBpY29uVXJsKGl0ZW06IEhlZHdpQXBwSXRlbSk6IHN0cmluZyB7XG4gICAgY29uc3QgYmFzZSA9ICh0aGlzLmJhc2VVcmwgfHwgJycpLnJlcGxhY2UoL1xcLyQvLCAnJyk7XG4gICAgY29uc3QgcGF0aCA9IGAke2Jhc2V9L21haWwvYXNzZXRzL2ltYWdlcy8ke2l0ZW0uaWNvbn1gO1xuICAgIHJldHVybiB0aGlzLmNhY2hlVHMgPyBgJHtwYXRofT92PSR7dGhpcy5jYWNoZVRzfWAgOiBwYXRoO1xuICB9XG5cbiAgbGlua0hyZWYoaXRlbTogSGVkd2lBcHBJdGVtKTogc3RyaW5nIHtcbiAgICBjb25zdCBiYXNlID0gKHRoaXMuYmFzZVVybCB8fCAnJykucmVwbGFjZSgvXFwvJC8sICcnKTtcbiAgICBjb25zdCBmdWxsUGF0aCA9IGJhc2UgPyBgJHtiYXNlfSR7aXRlbS5wYXRofWAgOiBpdGVtLnBhdGg7XG4gICAgY29uc3Qgc2VwID0gZnVsbFBhdGguaW5jbHVkZXMoJz8nKSA/ICcmJyA6ICc/JztcbiAgICByZXR1cm4gdGhpcy5jYWNoZVRzID8gYCR7ZnVsbFBhdGh9JHtzZXB9dj0ke3RoaXMuY2FjaGVUc31gIDogZnVsbFBhdGg7XG4gIH1cbn1cbiIsIjxkaXYgY2xhc3M9XCJoZWR3aS1hcHAtbGlzdFwiICpuZ0Zvcj1cImxldCBncm91cCBvZiBhcHBHcm91cHM7IHRyYWNrQnk6IHRyYWNrQnlHcm91cEluZGV4XCI+XG4gIDxkaXZcbiAgICBjbGFzcz1cImhlZHdpLWFwcC1pdGVtXCJcbiAgICBbY2xhc3MuZXh0cmFdPVwiZ3JvdXBbMF0/LmV4dHJhXCJcbiAgICAqbmdGb3I9XCJsZXQgaXRlbSBvZiBncm91cDsgdHJhY2tCeTogdHJhY2tCeUl0ZW1QYXRoXCJcbiAgPlxuICAgIDxhIFthdHRyLmhyZWZdPVwibGlua0hyZWYoaXRlbSlcIiB0YXJnZXQ9XCJfYmxhbmtcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJoZWR3aS1hcHAtbG9nb1wiPlxuICAgICAgICA8aW1nIFtzcmNdPVwiaWNvblVybChpdGVtKVwiIFthbHRdPVwiZGlzcGxheU5hbWUoaXRlbSlcIiAvPlxuICAgICAgPC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiaGVkd2ktYXBwLW5hbWVcIj5cbiAgICAgICAgPHNwYW4+e3sgZGlzcGxheU5hbWUoaXRlbSkgfX08L3NwYW4+XG4gICAgICA8L2Rpdj5cbiAgICA8L2E+XG4gIDwvZGl2PlxuPC9kaXY+XG4iXX0=
@@ -29,13 +29,18 @@ class AppMenuComponent {
29
29
  this.cacheTs = '';
30
30
  /** 应用列表,不传则使用默认列表 */
31
31
  this.apps = HEDWI_APP_MENU_DEFAULT_APPS;
32
+ /** 按 groupIndex 分组后的列表(仅当 apps 变化时重新计算,避免每次变更检测返回新数组导致图片循环请求) */
33
+ this.appGroups = [];
32
34
  }
33
- /** 返回应用显示名称 */
34
- displayName(item) {
35
- return item.name;
35
+ ngOnInit() {
36
+ this.appGroups = this.buildAppGroups();
36
37
  }
37
- /** 按 groupIndex 分组,用于按行渲染 */
38
- get appGroups() {
38
+ ngOnChanges(changes) {
39
+ if (changes['apps']) {
40
+ this.appGroups = this.buildAppGroups();
41
+ }
42
+ }
43
+ buildAppGroups() {
39
44
  const map = new Map();
40
45
  for (const item of this.apps) {
41
46
  const g = item.groupIndex ?? (item.extra ? 1 : 0);
@@ -46,9 +51,21 @@ class AppMenuComponent {
46
51
  const maxG = Math.max(0, ...map.keys());
47
52
  return Array.from({ length: maxG + 1 }, (_, i) => map.get(i) ?? []);
48
53
  }
54
+ /** trackBy:按分组索引,避免无意义的 DOM 重建 */
55
+ trackByGroupIndex(index, _group) {
56
+ return index;
57
+ }
58
+ /** trackBy:按 path 唯一标识应用项,避免无意义的 DOM 重建与图片重复请求 */
59
+ trackByItemPath(index, item) {
60
+ return item.path;
61
+ }
62
+ /** 返回应用显示名称 */
63
+ displayName(item) {
64
+ return item.name;
65
+ }
49
66
  iconUrl(item) {
50
67
  const base = (this.baseUrl || '').replace(/\/$/, '');
51
- const path = `${base}/static/mail/assets/images/${item.icon}`;
68
+ const path = `${base}/mail/assets/images/${item.icon}`;
52
69
  return this.cacheTs ? `${path}?v=${this.cacheTs}` : path;
53
70
  }
54
71
  linkHref(item) {
@@ -58,11 +75,11 @@ class AppMenuComponent {
58
75
  return this.cacheTs ? `${fullPath}${sep}v=${this.cacheTs}` : fullPath;
59
76
  }
60
77
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AppMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
61
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AppMenuComponent, selector: "hedwi-app-menu", inputs: { baseUrl: "baseUrl", cacheTs: "cacheTs", apps: "apps" }, ngImport: i0, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); }
78
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AppMenuComponent, selector: "hedwi-app-menu", inputs: { baseUrl: "baseUrl", cacheTs: "cacheTs", apps: "apps" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups; trackBy: trackByGroupIndex\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group; trackBy: trackByItemPath\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); }
62
79
  }
63
80
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AppMenuComponent, decorators: [{
64
81
  type: Component,
65
- args: [{ selector: 'hedwi-app-menu', standalone: false, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"] }]
82
+ args: [{ selector: 'hedwi-app-menu', standalone: false, template: "<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups; trackBy: trackByGroupIndex\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group; trackBy: trackByItemPath\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n", styles: [".hedwi-app-list{display:flex;padding:10px 5px;width:100%;overflow:auto;flex-wrap:wrap;box-sizing:border-box}.hedwi-app-item{width:80px;display:flex;flex-direction:column;box-sizing:border-box;padding:5px 0}.hedwi-app-item a{text-decoration:none;color:inherit}.hedwi-app-item:hover{background-color:var(--panel-bg-color, #f5f5f5);cursor:pointer}.hedwi-app-item div{display:flex;align-items:center;justify-content:center}.hedwi-app-item .hedwi-app-logo img{width:60px;height:60px}.hedwi-app-item .hedwi-app-name span{font-size:14px}.hedwi-app-item.extra .hedwi-app-logo img{padding:6px;box-sizing:border-box}:host-context(.app-menu){display:block}\n"] }]
66
83
  }], propDecorators: { baseUrl: [{
67
84
  type: Input
68
85
  }], cacheTs: [{
@@ -1 +1 @@
1
- {"version":3,"file":"hedwi-app-menu.mjs","sources":["../../../projects/hedwi-app-menu/src/lib/app-menu.types.ts","../../../projects/hedwi-app-menu/src/lib/app-menu.component.ts","../../../projects/hedwi-app-menu/src/lib/app-menu.component.html","../../../projects/hedwi-app-menu/src/lib/app-menu.module.ts","../../../projects/hedwi-app-menu/src/public-api.ts","../../../projects/hedwi-app-menu/src/hedwi-app-menu.ts"],"sourcesContent":["/**\n * 单个应用项配置,用于应用菜单库\n */\nexport interface HedwiAppItem {\n /** 应用路径,如 /mail, /chat */\n path: string;\n /** 图标文件名,如 mail.svg,相对于 baseUrl/static/mail/assets/images/ */\n icon: string;\n /** 显示名称 */\n name: string;\n /** i18n 键,如 \"Header|Mail\" */\n i18nKey?: string;\n /** 是否归类为「更多」应用(样式上可区分) */\n extra?: boolean;\n /** 分组索引:0=主应用行,1、2=更多应用行(用于多行展示) */\n groupIndex?: number;\n}\n\n/**\n * 默认应用列表,各项目可通过 apps 输入覆盖\n */\nexport const HEDWI_APP_MENU_DEFAULT_APPS: HedwiAppItem[] = [\n { path: '/mail', icon: 'mail.svg', name: 'Mail', i18nKey: 'Header|Mail', groupIndex: 0 },\n { path: '/contacts', icon: 'contacts.svg', name: 'Contacts', i18nKey: 'Header|Contacts', groupIndex: 0 },\n { path: '/calendar', icon: 'calendar.svg', name: 'Calendar', i18nKey: 'Header|Calendar', groupIndex: 0 },\n { path: '/ai', icon: 'ai.svg', name: 'AI', i18nKey: 'Header|AI', groupIndex: 0 },\n { path: '/chat', icon: 'chat.svg', name: 'Chat', i18nKey: 'Header|Chat', extra: true, groupIndex: 1 },\n { path: '/meet', icon: 'meet.svg', name: 'Meet', i18nKey: 'Header|Meet', extra: true, groupIndex: 1 },\n { path: '/kanban', icon: 'kanban.svg', name: 'Kanban', i18nKey: 'Header|Kanban', extra: true, groupIndex: 1 },\n { path: '/drive', icon: 'drive.svg', name: 'Drive', i18nKey: 'Header|Drive', extra: true, groupIndex: 1 },\n { path: '/docshome?type=doc', icon: 'docs.svg', name: 'Docs', i18nKey: 'Header|Docs', extra: true, groupIndex: 2 },\n { path: '/docshome?type=sheet', icon: 'sheets.svg', name: 'Sheets', i18nKey: 'Header|Sheets', extra: true, groupIndex: 2 },\n { path: '/docshome?type=slide', icon: 'slides.svg', name: 'Slides', i18nKey: 'Header|Slides', extra: true, groupIndex: 2 },\n { path: '/note', icon: 'note.svg', name: 'Note', i18nKey: 'Header|Note', extra: true, groupIndex: 2 },\n];\n","import { Component, Input } from '@angular/core';\nimport { HedwiAppItem, HEDWI_APP_MENU_DEFAULT_APPS } from './app-menu.types';\n\n@Component({\n selector: 'hedwi-app-menu',\n standalone: false,\n templateUrl: './app-menu.component.html',\n styleUrls: ['./app-menu.component.css'],\n})\nexport class AppMenuComponent {\n /** 静态资源基础 URL,如 environment.baseurl */\n @Input() baseUrl = '';\n /** 缓存时间戳,用于 URL 防缓存 */\n @Input() cacheTs = '';\n /** 应用列表,不传则使用默认列表 */\n @Input() apps: HedwiAppItem[] = HEDWI_APP_MENU_DEFAULT_APPS;\n\n /** 返回应用显示名称 */\n displayName(item: HedwiAppItem): string {\n return item.name;\n }\n\n /** 按 groupIndex 分组,用于按行渲染 */\n get appGroups(): HedwiAppItem[][] {\n const map = new Map<number, HedwiAppItem[]>();\n for (const item of this.apps) {\n const g = item.groupIndex ?? (item.extra ? 1 : 0);\n if (!map.has(g)) map.set(g, []);\n map.get(g)!.push(item);\n }\n const maxG = Math.max(0, ...map.keys());\n return Array.from({ length: maxG + 1 }, (_, i) => map.get(i) ?? []);\n }\n\n iconUrl(item: HedwiAppItem): string {\n const base = (this.baseUrl || '').replace(/\\/$/, '');\n const path = `${base}/static/mail/assets/images/${item.icon}`;\n return this.cacheTs ? `${path}?v=${this.cacheTs}` : path;\n }\n\n linkHref(item: HedwiAppItem): string {\n const base = (this.baseUrl || '').replace(/\\/$/, '');\n const fullPath = base ? `${base}${item.path}` : item.path;\n const sep = fullPath.includes('?') ? '&' : '?';\n return this.cacheTs ? `${fullPath}${sep}v=${this.cacheTs}` : fullPath;\n }\n}\n","<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { AppMenuComponent } from './app-menu.component';\n\n@NgModule({\n imports: [CommonModule],\n declarations: [AppMenuComponent],\n exports: [AppMenuComponent],\n})\nexport class HedwiAppMenuModule {}\n","/*\n * Public API Surface of hedwi-app-menu\n */\n\nexport { HedwiAppMenuModule } from './lib/app-menu.module';\nexport { AppMenuComponent } from './lib/app-menu.component';\nexport type { HedwiAppItem } from './lib/app-menu.types';\nexport { HEDWI_APP_MENU_DEFAULT_APPS } from './lib/app-menu.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hedwi-app-menu.mjs","sources":["../../../projects/hedwi-app-menu/src/lib/app-menu.types.ts","../../../projects/hedwi-app-menu/src/lib/app-menu.component.ts","../../../projects/hedwi-app-menu/src/lib/app-menu.component.html","../../../projects/hedwi-app-menu/src/lib/app-menu.module.ts","../../../projects/hedwi-app-menu/src/public-api.ts","../../../projects/hedwi-app-menu/src/hedwi-app-menu.ts"],"sourcesContent":["/**\n * 单个应用项配置,用于应用菜单库\n */\nexport interface HedwiAppItem {\n /** 应用路径,如 /mail, /chat */\n path: string;\n /** 图标文件名,如 mail.svg,相对于 baseUrl/static/mail/assets/images/ */\n icon: string;\n /** 显示名称 */\n name: string;\n /** i18n 键,如 \"Header|Mail\" */\n i18nKey?: string;\n /** 是否归类为「更多」应用(样式上可区分) */\n extra?: boolean;\n /** 分组索引:0=主应用行,1、2=更多应用行(用于多行展示) */\n groupIndex?: number;\n}\n\n/**\n * 默认应用列表,各项目可通过 apps 输入覆盖\n */\nexport const HEDWI_APP_MENU_DEFAULT_APPS: HedwiAppItem[] = [\n { path: '/mail', icon: 'mail.svg', name: 'Mail', i18nKey: 'Header|Mail', groupIndex: 0 },\n { path: '/contacts', icon: 'contacts.svg', name: 'Contacts', i18nKey: 'Header|Contacts', groupIndex: 0 },\n { path: '/calendar', icon: 'calendar.svg', name: 'Calendar', i18nKey: 'Header|Calendar', groupIndex: 0 },\n { path: '/ai', icon: 'ai.svg', name: 'AI', i18nKey: 'Header|AI', groupIndex: 0 },\n { path: '/chat', icon: 'chat.svg', name: 'Chat', i18nKey: 'Header|Chat', extra: true, groupIndex: 1 },\n { path: '/meet', icon: 'meet.svg', name: 'Meet', i18nKey: 'Header|Meet', extra: true, groupIndex: 1 },\n { path: '/kanban', icon: 'kanban.svg', name: 'Kanban', i18nKey: 'Header|Kanban', extra: true, groupIndex: 1 },\n { path: '/drive', icon: 'drive.svg', name: 'Drive', i18nKey: 'Header|Drive', extra: true, groupIndex: 1 },\n { path: '/docshome?type=doc', icon: 'docs.svg', name: 'Docs', i18nKey: 'Header|Docs', extra: true, groupIndex: 2 },\n { path: '/docshome?type=sheet', icon: 'sheets.svg', name: 'Sheets', i18nKey: 'Header|Sheets', extra: true, groupIndex: 2 },\n { path: '/docshome?type=slide', icon: 'slides.svg', name: 'Slides', i18nKey: 'Header|Slides', extra: true, groupIndex: 2 },\n { path: '/note', icon: 'note.svg', name: 'Note', i18nKey: 'Header|Note', extra: true, groupIndex: 2 },\n];\n","import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';\nimport { HedwiAppItem, HEDWI_APP_MENU_DEFAULT_APPS } from './app-menu.types';\n\n@Component({\n selector: 'hedwi-app-menu',\n standalone: false,\n templateUrl: './app-menu.component.html',\n styleUrls: ['./app-menu.component.css'],\n})\nexport class AppMenuComponent implements OnChanges, OnInit {\n /** 静态资源基础 URL,如 environment.baseurl */\n @Input() baseUrl = '';\n /** 缓存时间戳,用于 URL 防缓存 */\n @Input() cacheTs = '';\n /** 应用列表,不传则使用默认列表 */\n @Input() apps: HedwiAppItem[] = HEDWI_APP_MENU_DEFAULT_APPS;\n\n /** 按 groupIndex 分组后的列表(仅当 apps 变化时重新计算,避免每次变更检测返回新数组导致图片循环请求) */\n appGroups: HedwiAppItem[][] = [];\n\n ngOnInit(): void {\n this.appGroups = this.buildAppGroups();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['apps']) {\n this.appGroups = this.buildAppGroups();\n }\n }\n\n private buildAppGroups(): HedwiAppItem[][] {\n const map = new Map<number, HedwiAppItem[]>();\n for (const item of this.apps) {\n const g = item.groupIndex ?? (item.extra ? 1 : 0);\n if (!map.has(g)) map.set(g, []);\n map.get(g)!.push(item);\n }\n const maxG = Math.max(0, ...map.keys());\n return Array.from({ length: maxG + 1 }, (_, i) => map.get(i) ?? []);\n }\n\n /** trackBy:按分组索引,避免无意义的 DOM 重建 */\n trackByGroupIndex(index: number, _group: HedwiAppItem[]): number {\n return index;\n }\n\n /** trackBy:按 path 唯一标识应用项,避免无意义的 DOM 重建与图片重复请求 */\n trackByItemPath(index: number, item: HedwiAppItem): string {\n return item.path;\n }\n\n /** 返回应用显示名称 */\n displayName(item: HedwiAppItem): string {\n return item.name;\n }\n\n iconUrl(item: HedwiAppItem): string {\n const base = (this.baseUrl || '').replace(/\\/$/, '');\n const path = `${base}/mail/assets/images/${item.icon}`;\n return this.cacheTs ? `${path}?v=${this.cacheTs}` : path;\n }\n\n linkHref(item: HedwiAppItem): string {\n const base = (this.baseUrl || '').replace(/\\/$/, '');\n const fullPath = base ? `${base}${item.path}` : item.path;\n const sep = fullPath.includes('?') ? '&' : '?';\n return this.cacheTs ? `${fullPath}${sep}v=${this.cacheTs}` : fullPath;\n }\n}\n","<div class=\"hedwi-app-list\" *ngFor=\"let group of appGroups; trackBy: trackByGroupIndex\">\n <div\n class=\"hedwi-app-item\"\n [class.extra]=\"group[0]?.extra\"\n *ngFor=\"let item of group; trackBy: trackByItemPath\"\n >\n <a [attr.href]=\"linkHref(item)\" target=\"_blank\">\n <div class=\"hedwi-app-logo\">\n <img [src]=\"iconUrl(item)\" [alt]=\"displayName(item)\" />\n </div>\n <div class=\"hedwi-app-name\">\n <span>{{ displayName(item) }}</span>\n </div>\n </a>\n </div>\n</div>\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { AppMenuComponent } from './app-menu.component';\n\n@NgModule({\n imports: [CommonModule],\n declarations: [AppMenuComponent],\n exports: [AppMenuComponent],\n})\nexport class HedwiAppMenuModule {}\n","/*\n * Public API Surface of hedwi-app-menu\n */\n\nexport { HedwiAppMenuModule } from './lib/app-menu.module';\nexport { AppMenuComponent } from './lib/app-menu.component';\nexport type { HedwiAppItem } from './lib/app-menu.types';\nexport { HEDWI_APP_MENU_DEFAULT_APPS } from './lib/app-menu.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,16 +1,24 @@
1
+ import { OnChanges, OnInit, SimpleChanges } from '@angular/core';
1
2
  import { HedwiAppItem } from './app-menu.types';
2
3
  import * as i0 from "@angular/core";
3
- export declare class AppMenuComponent {
4
+ export declare class AppMenuComponent implements OnChanges, OnInit {
4
5
  /** 静态资源基础 URL,如 environment.baseurl */
5
6
  baseUrl: string;
6
7
  /** 缓存时间戳,用于 URL 防缓存 */
7
8
  cacheTs: string;
8
9
  /** 应用列表,不传则使用默认列表 */
9
10
  apps: HedwiAppItem[];
11
+ /** 按 groupIndex 分组后的列表(仅当 apps 变化时重新计算,避免每次变更检测返回新数组导致图片循环请求) */
12
+ appGroups: HedwiAppItem[][];
13
+ ngOnInit(): void;
14
+ ngOnChanges(changes: SimpleChanges): void;
15
+ private buildAppGroups;
16
+ /** trackBy:按分组索引,避免无意义的 DOM 重建 */
17
+ trackByGroupIndex(index: number, _group: HedwiAppItem[]): number;
18
+ /** trackBy:按 path 唯一标识应用项,避免无意义的 DOM 重建与图片重复请求 */
19
+ trackByItemPath(index: number, item: HedwiAppItem): string;
10
20
  /** 返回应用显示名称 */
11
21
  displayName(item: HedwiAppItem): string;
12
- /** 按 groupIndex 分组,用于按行渲染 */
13
- get appGroups(): HedwiAppItem[][];
14
22
  iconUrl(item: HedwiAppItem): string;
15
23
  linkHref(item: HedwiAppItem): string;
16
24
  static ɵfac: i0.ɵɵFactoryDeclaration<AppMenuComponent, never>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedwi-app-menu",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^16.2.0",
6
6
  "@angular/core": "^16.2.0"