瀏覽代碼

提出为组件,不能排序和筛选

qdy 1 月之前
當前提交
0a7f9f04a9

+ 7
- 6
src/app/app.component.ts 查看文件

@@ -40,12 +40,13 @@ export class AppComponent implements OnInit, OnDestroy {
40 40
     console.log('AppComponent初始化');
41 41
     console.log('当前路由:', this.router.url);
42 42
     
43
-    // 禁用模拟数据,使用真实API
44
-    this.config.useMockData = false;
45
-    console.log('使用真实API,配置:', {
46
-      useMockData: this.config.useMockData,
47
-      apiBaseUrl: this.config.apiBaseUrl
48
-    });
43
+     // 禁用模拟数据,使用真实API
44
+     this.config.useMockData = false;
45
+     this.config.apiBaseUrl = '/api'; // 确保使用代理路径
46
+     console.log('使用真实API,配置:', {
47
+       useMockData: this.config.useMockData,
48
+       apiBaseUrl: this.config.apiBaseUrl
49
+     });
49 50
     
50 51
     // 从本地存储恢复宽度
51 52
     const savedWidth = localStorage.getItem('sidebarWidth');

+ 27
- 14
src/app/pages/service-register-config/service-register-config.component.html 查看文件

@@ -1,15 +1,28 @@
1 1
 <!-- 顶部标题区域 -->
2
-  <app-sticky-header
3
-    [title]="title"
4
-    [hintText]="hintText"
5
-    [buttons]="headerButtons"
6
-    [showDebugInfo]="true"
7
-    [autoDetect]="true"
8
-    [scrollContainer]="'.content-area'"
9
-    [scrollThreshold]="20"
10
-    [compactThreshold]="80"
11
-    [widthTarget]="'#tabulator-table'"
12
-    (buttonAction)="onHeaderButtonAction($event)"
13
-  ></app-sticky-header>
14
-  <!-- Tabulator 容器 -->
15
-  <div id="tabulator-table" #matCard class="flex-1" ></div>
2
+<app-sticky-header
3
+  [title]="title"
4
+  [hintText]="hintText"
5
+  [buttons]="headerButtons"
6
+  [showDebugInfo]="true"
7
+  [autoDetect]="true"
8
+  [scrollContainer]="'.content-area'"
9
+  [scrollThreshold]="20"
10
+  [compactThreshold]="80"
11
+ [widthTarget]="'#tabulator-table'"
12
+  (buttonAction)="onHeaderButtonAction($event)"
13
+></app-sticky-header>
14
+
15
+<!-- Tabulator 表格组件 -->
16
+<app-tabulator-grid id="tabulator-table" #matCard
17
+  [columns]="columns"
18
+  [dataLoader]="dataLoader"
19
+  [height]="'600px'"
20
+  [paginationSize]="20"
21
+  [paginationSizeSelector]="[10, 20, 50, 100,200,500]"
22
+  [showPagination]="true"
23
+  [remoteSort]="true"
24
+  [remoteFilter]="true"
25
+  (dataLoaded)="onDataLoaded($event)"
26
+  (ajaxError)="onAjaxError($event)"
27
+  class="flex-1"
28
+></app-tabulator-grid>

+ 103
- 93
src/app/pages/service-register-config/service-register-config.component.spec.ts 查看文件

@@ -1,36 +1,55 @@
1
-import { ComponentFixture, TestBed } from '@angular/core/testing';
1
+import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
2
+import { HttpClientTestingModule } from '@angular/common/http/testing';
2 3
 import { ServiceRegisterConfigComponent } from './service-register-config.component';
3 4
 import { ConfigMetaService } from '../../services/config-meta.service';
4
-import { MatSnackBar } from '@angular/material/snack-bar';
5
+import { AuthService } from '../../services/auth.service';
5 6
 import { ConfigService } from 'base-core';
6 7
 import { of } from 'rxjs';
7 8
 
9
+// 模拟全局 Tabulator 对象
10
+const mockTabulator = {
11
+  new: jasmine.createSpy('Tabulator.new').and.callFake(() => ({
12
+    replaceData: jasmine.createSpy('replaceData'),
13
+    clearFilter: jasmine.createSpy('clearFilter'),
14
+    clearSort: jasmine.createSpy('clearSort'),
15
+    download: jasmine.createSpy('download'),
16
+    getPage: jasmine.createSpy('getPage').and.returnValue(1),
17
+    getPageSize: jasmine.createSpy('getPageSize').and.returnValue(20),
18
+    getFilters: jasmine.createSpy('getFilters').and.returnValue([]),
19
+    getSorters: jasmine.createSpy('getSorters').and.returnValue([])
20
+  }))
21
+};
22
+
8 23
 describe('ServiceRegisterConfigComponent', () => {
9 24
   let component: ServiceRegisterConfigComponent;
10 25
   let fixture: ComponentFixture<ServiceRegisterConfigComponent>;
11 26
   let configMetaServiceSpy: jasmine.SpyObj<ConfigMetaService>;
27
+  let authServiceSpy: jasmine.SpyObj<AuthService>;
12 28
   let configServiceSpy: jasmine.SpyObj<ConfigService>;
13
-  let snackBarSpy: jasmine.SpyObj<MatSnackBar>;
14 29
 
15 30
   beforeEach(async () => {
31
+    // 设置全局 Tabulator 模拟
32
+    (window as any).Tabulator = mockTabulator.new;
33
+
16 34
     configMetaServiceSpy = jasmine.createSpyObj('ConfigMetaService', [
17 35
       'initConfigMeta',
18
-      'listConfigMetaPaginated',
36
+      'listForTabulator',
19 37
       'getInitUrl',
20 38
       'getListUrl'
21 39
     ]);
40
+    authServiceSpy = jasmine.createSpyObj('AuthService', ['getBasicAuth']);
41
+    authServiceSpy.getBasicAuth.and.returnValue(null);
22 42
     configServiceSpy = jasmine.createSpyObj('ConfigService', [], {
23 43
       useMockData: false,
24 44
       apiBaseUrl: 'http://localhost:8080'
25 45
     });
26
-    snackBarSpy = jasmine.createSpyObj('MatSnackBar', ['open']);
27 46
 
28 47
     await TestBed.configureTestingModule({
29
-      imports: [ServiceRegisterConfigComponent],
48
+      imports: [ServiceRegisterConfigComponent, HttpClientTestingModule],
30 49
       providers: [
31 50
         { provide: ConfigMetaService, useValue: configMetaServiceSpy },
32
-        { provide: ConfigService, useValue: configServiceSpy },
33
-        { provide: MatSnackBar, useValue: snackBarSpy }
51
+        { provide: AuthService, useValue: authServiceSpy },
52
+        { provide: ConfigService, useValue: configServiceSpy }
34 53
       ]
35 54
     }).compileComponents();
36 55
 
@@ -39,130 +58,121 @@ describe('ServiceRegisterConfigComponent', () => {
39 58
     fixture.detectChanges();
40 59
   });
41 60
 
61
+  afterEach(() => {
62
+    // 清理全局模拟
63
+    delete (window as any).Tabulator;
64
+  });
65
+
42 66
   it('should create', () => {
43 67
     expect(component).toBeTruthy();
44 68
   });
45 69
 
46 70
   it('should initialize with default values', () => {
47
-    expect(component.tabulatorColumns).toBeDefined();
48
-    expect(component.tabulatorDataSource).toBeDefined();
49
-    expect(component.isDataLoading).toBeFalse();
50
-    expect(component.dataCount).toBe(0);
51
-    expect(component.pageSize).toBe(20);
71
+    expect(component.title).toBe('注册服务配置');
72
+    expect(component.hintText).toBe('点击"注册"按钮将同步所有配置元信息到数据库,并显示配置列表。');
73
+    expect(component.headerButtons.length).toBe(2);
74
+    expect(component.headerButtons[0].name).toBe('register');
75
+    expect(component.headerButtons[1].name).toBe('refresh');
52 76
   });
53 77
 
54 78
   it('should call initConfigMeta and handle success response', () => {
55
-    const mockResponse = { success: true, data: '初始化成功' };
79
+    const mockResponse = { success: true, data: '模拟初始化成功' };
56 80
     configMetaServiceSpy.initConfigMeta.and.returnValue(of(mockResponse));
57 81
 
58
-    component.initConfigMeta();
82
+    component.onRegister();
59 83
 
60 84
     expect(configMetaServiceSpy.initConfigMeta).toHaveBeenCalled();
61
-    expect(component.isDataLoading).toBeFalse();
62
-    expect(snackBarSpy.open).toHaveBeenCalledWith('配置元信息初始化成功', '关闭', { duration: 3000 });
85
+    expect(component.headerButtons[0].loading).toBeFalse();
63 86
   });
64 87
 
65 88
   it('should call initConfigMeta and handle error response', () => {
66
-    const mockResponse = { success: false, message: '初始化失败' };
67
-    configMetaServiceSpy.initConfigMeta.and.returnValue(of(mockResponse));
68
-
69
-    component.initConfigMeta();
70
-
71
-    expect(configMetaServiceSpy.initConfigMeta).toHaveBeenCalled();
72
-    expect(snackBarSpy.open).toHaveBeenCalledWith('初始化失败: 初始化失败', '关闭', { duration: 5000 });
73
-  });
74
-
75
-  it('should call initConfigMeta and handle request error', () => {
76 89
     const mockError = new Error('请求失败');
77
-    configMetaServiceSpy.initConfigMeta.and.throwError(mockError);
90
+    configMetaServiceSpy.initConfigMeta.and.returnValue(of(mockError));
78 91
 
79
-    component.initConfigMeta();
92
+    component.onRegister();
80 93
 
81 94
     expect(configMetaServiceSpy.initConfigMeta).toHaveBeenCalled();
82
-    expect(component.isDataLoading).toBeFalse();
95
+    expect(component.headerButtons[0].loading).toBeFalse();
83 96
   });
84 97
 
85
-  it('should update debug info on init', () => {
86
-    configMetaServiceSpy.getInitUrl.and.returnValue('http://localhost:8080/init/config/meta');
87
-    configMetaServiceSpy.getListUrl.and.returnValue('http://localhost:8080/config/meta/list');
98
+  it('should handle header button actions', () => {
99
+    spyOn(component, 'onRegister');
100
+    spyOn(component, 'refresh');
101
+
102
+    component.onHeaderButtonAction('register');
103
+    expect(component.onRegister).toHaveBeenCalled();
88 104
 
89
-    component.ngOnInit();
105
+    component.onHeaderButtonAction('refresh');
106
+    expect(component.refresh).toHaveBeenCalled();
90 107
 
91
-    expect(component.debugInfo.registerUrl).toBe('http://localhost:8080/init/config/meta');
92
-    expect(component.debugInfo.listUrl).toBe('http://localhost:8080/config/meta/list');
93
-    expect(component.debugInfo.useMockData).toBeFalse();
94
-    expect(component.debugInfo.dataSource).toBe('真实 API');
108
+    component.onHeaderButtonAction('unknown');
109
+    // 应该记录警告但无错误
95 110
   });
96 111
 
97
-  it('should handle tabulator data request', () => {
98
-    const mockParams = {
99
-      page: 1,
100
-      size: 20,
101
-      sorters: [],
102
-      filters: []
112
+  it('should refresh table data', () => {
113
+    // 模拟 Tabulator 实例
114
+    const mockTableInstance = {
115
+      replaceData: jasmine.createSpy('replaceData')
103 116
     };
104
-    const mockResponse = {
105
-      success: true,
106
-      data: [{ id: '1', configName: 'test' }],
107
-      totalCount: 1
108
-    };
109
-    configMetaServiceSpy.listConfigMetaPaginated.and.returnValue(of(mockResponse));
117
+    (component as any).tabulator = mockTableInstance;
110 118
 
111
-    const result = component.onTabulatorDataRequest(mockParams);
119
+    component.refresh();
112 120
 
113
-    expect(configMetaServiceSpy.listConfigMetaPaginated).toHaveBeenCalled();
114
-    expect(component.dataCount).toBe(1);
115
-    expect(component.totalCount).toBe(1);
116
-    expect(component.debugInfo.recordCount).toBe(1);
121
+    expect(mockTableInstance.replaceData).toHaveBeenCalled();
122
+    expect(component.headerButtons[1].loading).toBeTrue();
117 123
   });
118 124
 
119
-  it('should handle tabulator data request with error', () => {
120
-    const mockParams = {
121
-      page: 1,
122
-      size: 20,
123
-      sorters: [],
124
-      filters: []
125
+  it('should reset table filters and sort', () => {
126
+    const mockTableInstance = {
127
+      clearFilter: jasmine.createSpy('clearFilter'),
128
+      clearSort: jasmine.createSpy('clearSort')
125 129
     };
126
-    const mockError = new Error('请求失败');
127
-    configMetaServiceSpy.listConfigMetaPaginated.and.throwError(mockError);
130
+    (component as any).tabulator = mockTableInstance;
128 131
 
129
-    const result = component.onTabulatorDataRequest(mockParams);
132
+    component.reset();
130 133
 
131
-    expect(configMetaServiceSpy.listConfigMetaPaginated).toHaveBeenCalled();
132
-    expect(component.isDataLoading).toBeFalse();
133
-    expect(snackBarSpy.open).toHaveBeenCalledWith('数据加载请求失败: ' + mockError.message, '关闭', { duration: 5000 });
134
+    expect(mockTableInstance.clearFilter).toHaveBeenCalled();
135
+    expect(mockTableInstance.clearSort).toHaveBeenCalled();
134 136
   });
135 137
 
136
-  it('should update debug info when page changes', () => {
137
-    const mockEvent = { page: 2, pageSize: 20 };
138
-    component.onPageChanged(mockEvent);
139
-
140
-    expect(component.currentPage).toBe(2);
141
-    expect(component.pageSize).toBe(20);
142
-    expect(component.debugInfo.currentPage).toBe(2);
143
-  });
144
-
145
-  it('should copy debug info to clipboard', () => {
146
-    spyOn(navigator.clipboard, 'writeText').and.returnValue(Promise.resolve());
147
-    component.debugInfo = {
148
-      dataSource: 'Mock 数据',
149
-      recordCount: 10,
150
-      lastUpdated: '2024-01-01 12:00:00',
151
-      useMockData: true,
152
-      registerUrl: 'http://localhost:8080/init',
153
-      listUrl: 'http://localhost:8080/list',
154
-      currentPage: 1,
155
-      totalPages: 1
138
+  it('should export data as CSV', () => {
139
+    const mockTableInstance = {
140
+      download: jasmine.createSpy('download')
156 141
     };
142
+    (component as any).tabulator = mockTableInstance;
157 143
 
158
-    component.copyDebugInfo();
144
+    component.exportData();
159 145
 
160
-    expect(navigator.clipboard.writeText).toHaveBeenCalled();
161
-    expect(snackBarSpy.open).toHaveBeenCalledWith('调试信息已复制到剪贴板', '关闭', { duration: 2000 });
146
+    expect(mockTableInstance.download).toHaveBeenCalledWith('csv', 'config-meta-data.csv');
162 147
   });
163 148
 
164
-  it('should refresh data and update debug info', () => {
165
-    component.refreshData();
166
-    expect(component.isDataLoading).toBeTrue();
149
+  it('should create ajax request function that calls listForTabulator', fakeAsync(() => {
150
+    const mockParams = { page: 1, size: 20, sort: [], filter: [] };
151
+    const mockResponse = { last_page: 5, data: [{ id: '1', configName: 'test' }] };
152
+    
153
+    configMetaServiceSpy.listForTabulator.and.returnValue(of(mockResponse));
154
+    
155
+    const requestFunc = (component as any).createAjaxRequestFunc();
156
+    
157
+    let resolvedResponse: any;
158
+    requestFunc('any-url', {}, mockParams).then((response: any) => {
159
+      resolvedResponse = response;
160
+    });
161
+    
162
+    tick(); // 处理异步 observable
163
+    
164
+    expect(configMetaServiceSpy.listForTabulator).toHaveBeenCalledWith(mockParams);
165
+    expect(resolvedResponse).toEqual(mockResponse);
166
+  }));
167
+
168
+  it('should have template alias methods', () => {
169
+    spyOn(component, 'refresh');
170
+    spyOn(component, 'reset');
171
+
172
+    component.refreshTable();
173
+    expect(component.refresh).toHaveBeenCalled();
174
+
175
+    component.resetTable();
176
+    expect(component.reset).toHaveBeenCalled();
167 177
   });
168 178
 });

+ 176
- 282
src/app/pages/service-register-config/service-register-config.component.ts 查看文件

@@ -1,313 +1,184 @@
1 1
 
2
-import { Component, AfterViewInit, inject } from '@angular/core';
2
+import { Component, inject, ViewChild, OnInit, AfterViewInit } from '@angular/core';
3 3
 import { ConfigMetaService } from '../../services/config-meta.service';
4
-import { AuthService } from '../../services/auth.service';
5
-import { TabulatorMapper } from '../../utils/tabulator-mapper.util';
6
-import { ConfigService, StickyHeaderComponent, StickyHeaderButton } from 'base-core';
4
+import { ConfigService, StickyHeaderComponent, StickyHeaderButton, TabulatorGridComponent } from 'base-core';
7 5
 import { CommonModule } from '@angular/common';
8
-
9
-// 声明全局类型
10
-declare var Tabulator: any;
11
-
6
+import { Observable } from 'rxjs';
12 7
 
13 8
 @Component({
14
-  imports: [CommonModule, StickyHeaderComponent],
9
+  imports: [CommonModule, StickyHeaderComponent, TabulatorGridComponent],
15 10
   selector: 'app-service-register-config',
16 11
   templateUrl: './service-register-config.component.html',
17 12
   styleUrl: './service-register-config.component.scss'
18 13
 })
19
-export class ServiceRegisterConfigComponent implements AfterViewInit {
20
-  private tabulator!: any;
21
-  private totalCount: number = 0;
14
+export class ServiceRegisterConfigComponent implements OnInit, AfterViewInit {
22 15
   private configMetaService = inject(ConfigMetaService);
23 16
   private config = inject(ConfigService);
24
-  private authService = inject(AuthService);
25
-  
26
-   title = '注册服务配置';
27
-   hintText = '点击"注册"按钮将同步所有配置元信息到数据库,并显示配置列表。';
28
-   headerButtons: StickyHeaderButton[] = [
29
-     { title: '注册', name: 'register', icon: 'add', color: 'primary' },
30
-     { title: '刷新', name: 'refresh', icon: 'refresh', color: 'accent' }
31
-    ];
32
-    //registering = false;
33
-    
34
-    // 外部查询条件预留字段(用于将来扩展)
35
-    externalFilters: any = {};
17
+
18
+  @ViewChild(TabulatorGridComponent) tabulatorGrid!: TabulatorGridComponent;
19
+
20
+   ngOnInit(): void {
21
+     console.log('ServiceRegisterConfigComponent 初始化');
22
+     // 确保使用代理API路径
23
+     this.config.apiBaseUrl = '/api';
24
+     console.log('ConfigService:', {
25
+       useMockData: this.config.useMockData,
26
+       apiBaseUrl: this.config.apiBaseUrl
27
+     });
28
+   }
36 29
 
37 30
   ngAfterViewInit(): void {
38
-    console.log('ServiceRegisterConfigComponent ngAfterViewInit');
39
-    console.log('Tabulator global available:', typeof Tabulator !== 'undefined');
40
-    console.log('ConfigService状态:', {
41
-      useMockData: this.config.useMockData,
42
-      apiBaseUrl: this.config.apiBaseUrl
43
-    });
44
-    console.log('ConfigMetaService API URL:', this.configMetaService.getListUrl());
45
-    
46
-    // 使用 setTimeout 确保 DOM 已渲染
47
-    setTimeout(() => {
48
-      console.log('Initializing Tabulator...');
49
-      this.initTabulator();
50
-    }, 100);
31
+    console.log('👀 ServiceRegisterConfigComponent: TabulatorGrid 视图初始化完成');
51 32
   }
33
+
34
+  title = '注册服务配置';
35
+  hintText = '点击"注册"按钮将同步所有配置元信息到数据库,并显示配置列表。';
36
+  headerButtons: StickyHeaderButton[] = [
37
+    { title: '注册', name: 'register', icon: 'add', color: 'primary' },
38
+    { title: '刷新', name: 'refresh', icon: 'refresh', color: 'accent' }
39
+  ];
52 40
   
53
-  /**
54
-   * 初始化表格 - 标准远程分页版本
55
-   */
56
-  private initTabulator(): void {
57
-    console.log('initTabulator called');
58
-    console.log('Table element exists:', document.getElementById('tabulator-table'));
59
-    
60
-    // 获取Basic认证信息
61
-    const basicAuth = this.authService.getBasicAuth();
62
-    console.log('Basic认证信息:', basicAuth ? '有' : '无');
63
-    
64
-    this.tabulator = new Tabulator('#tabulator-table', {
65
-     
66
-      // 列定义 - 使用现有列配置
67
-    columns: [
68
-      
69
-      { title: 'ID', field: 'id', sorter: 'number', headerFilter: 'input', width: 220 ,
70
-        headerMenu: [
71
-      {
72
-        label: "<i class='fa fa-filter'></i> 切换筛选",
73
-        action: (column:any) => {
74
-          const headerFilterEl = column.getElement().querySelector('.tabulator-header-filter');
75
-          if (headerFilterEl) {
76
-            const isHidden = headerFilterEl.style.display === 'none';
77
-            headerFilterEl.style.display = isHidden ? '' : 'none';
41
+  // 外部查询条件预留字段(用于将来扩展)
42
+  externalFilters: any = {};
43
+
44
+  // 表格列定义
45
+  columns = [
46
+    { 
47
+      title: 'ID', 
48
+      field: 'id', 
49
+      sorter: 'number', 
50
+      headerFilter: 'input', 
51
+      width: 220,
52
+      headerMenu: [
53
+        {
54
+          label: "<i class='fa fa-filter'></i> 切换筛选",
55
+          action: (column: any) => {
56
+            const headerFilterEl = column.getElement().querySelector('.tabulator-header-filter');
57
+            if (headerFilterEl) {
58
+              const isHidden = headerFilterEl.style.display === 'none';
59
+              headerFilterEl.style.display = isHidden ? '' : 'none';
60
+            }
78 61
           }
79 62
         }
80
-      }
81
-    ]
82
-      },
83
-      { title: '配置名称', field: 'configName', sorter: 'string', headerFilter: 'input', headerFilterFunc: 'like',width: 110 },
84
-      { title: '字段名', field: 'fieldName', sorter: 'string', headerFilter: 'input', headerFilterFunc: 'like' ,width: 130},
85
-      { title: '字段类型', field: 'fieldType', sorter: 'string', headerFilter: 'input',width: 120, headerFilterParams: { values: ['string', 'number', 'boolean', 'array', 'object'] } },
86
-      { title: '描述', field: 'fieldDesc', sorter: 'string', headerFilter: 'input', headerFilterFunc: 'like' }
87
-    ],
88
-      
89
-      // 标准远程分页配置
90
-      pagination: true,
91
-      paginationMode: 'remote',
92
-      paginationSize: 20,
93
-      paginationSizeSelector: [10, 20, 50, 100],
94
-      sortMode: 'remote',
95
-      filterMode: 'remote',
96
-      
97
-      // 使用标准AJAX配置,通过代理调用后端API
98
-      ajaxURL: '/api/config/meta/list',
99
-      ajaxConfig: {
100
-        method: 'POST',
101
-        headers: {
102
-          'Authorization': basicAuth ? `Basic ${basicAuth}` : '',
103
-          'Content-Type': 'application/json'
104
-        }
105
-      },
106
-      ajaxContentType: 'json',
107
-      
108
-      ajaxParams: function() {
109
-        // 获取调用时的上下文信息
110
-        const callTime = new Date().toISOString();
111
-        const stack = new Error().stack; // 获取调用堆栈
112
-        
113
-        console.group("=== ajaxParams被调用 ===");
114
-        console.log("调用时间:", callTime);
115
-        console.log("当前页码:", this.getPage());
116
-        console.log("筛选条件:", this.getFilters(true));
117
-        console.log("排序条件:", this.getSorters());
118
-       // console.log("调用来源:", stack.split('\n')[2]?.trim() || "未知");
119
-        console.groupEnd();
120
-        
121
-        return {
122
-            page: this.getPage() || 1,
123
-            size: this.getPageSize() || 20,
124
-            filter: this.getFilters(true) || [],
125
-            sort: this.getSorters() || []
126
-        };
63
+      ]
127 64
     },
128
-      //       参数生成器,将Tabulator参数转换为后端期望格式
129
-      ajaxURLGenerator:function(url: string, config: any, params: any){
130
-      //ajaxParamsGenerator: (url: string, config: any, params: any) => {
131
-        console.log('=== Tabulator AJAX参数生成器被调用 ===');
132
-        console.log('请求URL:', url);
133
-        console.log('请求配置:', config);
134
-        console.log('原始params类型:', typeof params);
135
-        console.log('原始params完整结构:', JSON.stringify(params, null, 2));
136
-        
137
-        // 详细分析params对象
138
-        if (params) {
139
-          console.log('params所有键:', Object.keys(params));
140
-          console.log('page:', params.page);
141
-          console.log('size:', params.size);
142
-          console.log('sort类型:', typeof params.sort);
143
-          console.log('sort值:', params.sort);
144
-          console.log('filter类型:', typeof params.filter);
145
-          console.log('filter值:', params.filter);
146
-          
147
-          // 检查可能的别名
148
-          console.log('sorters存在?:', 'sorters' in params);
149
-          console.log('filters存在?:', 'filters' in params);
150
-          
151
-          // 检查是否有外部查询条件的预留字段
152
-          console.log('当前外部查询条件预留字段:', this.externalFilters || '未定义');
153
-        } else {
154
-          console.log('params为undefined或null');
155
-          params = {};
156
-        }
157
-        
158
-        // 构建请求参数,使用TabulatorMapper转换为后端期望格式
159
-        // Tabulator 6.x发送的是 sort/filter(单数),但后端期望 sorters/filters(复数)
160
-       // const request = TabulatorMapper.buildServerRequest(
161
-         // params.page || 1,
162
-          //params.size || 20,
163
-          //params.sorts || [],      // Tabulator使用单数'sort'
164
-          //params.filters || []     // Tabulator使用单数'filter'
165
-        //);
166
-        
167
-       // console.log('生成的请求参数:', JSON.stringify(request, null, 2));
168
-       
169
-         const fullUrl = url + '?params=' + encodeURIComponent(JSON.stringify(params));
65
+    { 
66
+      title: '配置名称', 
67
+      field: 'configName', 
68
+      sorter: 'string', 
69
+      headerFilter: 'input', 
70
+      headerFilterFunc: 'like', 
71
+      width: 110 
72
+    },
73
+    { 
74
+      title: '字段名', 
75
+      field: 'fieldName', 
76
+      sorter: 'string', 
77
+      headerFilter: 'input', 
78
+      headerFilterFunc: 'like', 
79
+      width: 130 
80
+    },
81
+    { 
82
+      title: '字段类型', 
83
+      field: 'fieldType', 
84
+      sorter: 'string', 
85
+      headerFilter: 'input', 
86
+      width: 120, 
87
+      headerFilterParams: { values: ['string', 'number', 'boolean', 'array', 'object'] } 
88
+    },
89
+    { 
90
+      title: '描述', 
91
+      field: 'fieldDesc', 
92
+      sorter: 'string', 
93
+      headerFilter: 'input', 
94
+      headerFilterFunc: 'like' 
95
+    }
96
+  ];
170 97
 
171
-         console.log('完整URL:', fullUrl);
172
-          console.log('=== 参数生成器执行结束 ===');
173
-  
174
-        return fullUrl;
98
+  /**
99
+   * 数据加载函数 - 适配 TabulatorGridComponent
100
+   */
101
+  dataLoader = (params: any): Observable<{ last_page: number; data: any[] }> => {
102
+    console.log('📊 ServiceRegisterConfigComponent 数据加载函数被调用');
103
+    console.log('原始参数:', params);
104
+    console.log('ConfigMetaService URL:', this.configMetaService.getListUrl());
175 105
     
176
-        // 返回完整的请求配置对象
177
-  // return {
178
-  //   method: 'POST',  // 使用POST方法
179
-  //   headers: {
180
-  //     'Content-Type': 'application/json',
181
-  //     'Authorization': 'Basic YWRtaW46MTIz',  // 你的认证信息
182
-  //     // 可以添加其他需要的headers
183
-  //   },
184
-  //   body: JSON.stringify({
185
-  //     filter: params.filter || [],  // 根据后端期望的字段名调整
186
-  //     page: params.page || 1,
187
-  //     size: params.size || 20,
188
-  //     sort: params.sorter || []     // 根据后端期望的字段名调整
189
-   //  })
190
-  //};
191
-      },
192
-      
193
-      // 响应处理,将后端响应转换为Tabulator期望格式
194
-      // ajaxResponse: (url: string, params: any, response: any) => {
195
-      //   console.log('Tabulator AJAX响应处理', { url, params, response });
196
-        
197
-      //   // 检查响应格式
198
-      //   if (response && typeof response === 'object') {
199
-      //     // 后端返回格式:{success: true, data: [...], totalCount: N, last_page: N}
200
-      //     const data = response.data || [];
201
-      //     const totalCount = response.totalCount || 0;
202
-      //     const pageSize = params.size || 20;
203
-      //     const lastPage = response.last_page || Math.ceil(totalCount / pageSize);
204
-          
205
-      //     return {
206
-      //       data: data,
207
-      //       last_page: lastPage
208
-      //     };
209
-      //   }
210
-        
211
-      //   // 如果响应格式不符合预期,返回空数据
212
-      //   console.warn('响应格式不符合预期:', response);
213
-      //   return {
214
-      //     data: [],
215
-      //     last_page: 0
216
-      //   };
217
-      // },
218
-      
219
-      tableAjaxError: (error: any) => {
220
-        console.error('Tabulator AJAX错误:', error);
221
-        if (error.status === 401 || error.status === 403) {
222
-          console.warn('认证失败,跳转到登录页面');
223
-          this.authService.logout();
106
+    // 确保参数有默认值
107
+    const safeParams = {
108
+      page: params?.page || 1,
109
+      size: params?.size || 20,
110
+      sort: params?.sort || [],
111
+      filter: params?.filter || []
112
+    };
113
+    
114
+    console.log('处理后参数:', safeParams);
115
+    
116
+    return this.configMetaService.listForTabulator(safeParams);
117
+  };
118
+
119
+  /**
120
+   * 注册配置元信息
121
+   */
122
+  onRegister(): void {
123
+    console.log('注册按钮被点击');
124
+    this.headerButtons[0].loading = true;
125
+
126
+    this.configMetaService.initConfigMeta().subscribe({
127
+      next: (result: any) => {
128
+        this.headerButtons[0].loading = false;
129
+        if (result.success) {
130
+          console.log('注册完成:', result.data);
131
+          // 注册成功后刷新表格数据
132
+          this.refresh();
133
+          console.log('注册成功,数据已重新加载');
134
+        } else {
135
+          console.error('注册失败:', result);
224 136
         }
225 137
       },
226
-      
227
-
228
-      
229
-      // 表格样式
230
-      layout: 'fitColumns',
231
-      responsiveLayout: 'collapse',
232
-      //height: '800px',
233
-      
234
-      // 初始加载
235
-      ajaxInitialLoad: true,
236
-      
237
-      // 分页位置
238
-      paginationCounter: 'rows',
239
-      paginationButtonCount: 5
138
+      error: (error) => {
139
+        this.headerButtons[0].loading = false;
140
+        console.error('注册请求失败:', error);
141
+      }
240 142
     });
241 143
   }
242
-  
243 144
 
244
-   onRegister() {
245
-      console.log('注册按钮被点击');
246
-      //this.registering = true;
247
-      this.headerButtons[0].loading = true;
248
-      
249
-      this.configMetaService.initConfigMeta().subscribe({
250
-        next: (result: any) => {
251
-         // this.registering = false;
252
-          this.headerButtons[0].loading = false;
253
-          //this.errorMessage = null;
254
-          if (result.success) {
255
-            console.log('注册完成:', result.data);
256
-            
257
-            // 注册后切换到真实API数据
258
-            //this.debugInfo.useMockData = false;
259
-            //this.updateDataConfig();
260
-            
261
-            // 重新加载数据
262
-            //this.loadData(1);
263
-            
264
-            console.log('注册成功,数据已重新加载');
265
-          } else {
266
-            console.error('注册失败:', result);
267
-          }
268
-        },
269
-        error: (error) => {
270
-         // this.registering = false;
271
-          this.headerButtons[0].loading = false;
272
-          console.error('注册请求失败:', error);
273
-        }
274
-      });
145
+  /**
146
+   * 处理头部按钮点击事件
147
+   */
148
+  onHeaderButtonAction(name: string): void {
149
+    console.log(`头部按钮点击: ${name}`);
150
+    switch (name) {
151
+      case 'register':
152
+        this.onRegister();
153
+        break;
154
+      case 'refresh':
155
+        this.refresh();
156
+        break;
157
+      default:
158
+        console.warn(`未知按钮操作: ${name}`);
275 159
     }
276
-    
277
-    onHeaderButtonAction(name: string) {
278
-      console.log(`头部按钮点击: ${name}`);
279
-      switch (name) {
280
-        case 'register':
281
-          this.onRegister();
282
-          break;
283
-        case 'refresh':
284
-          this.refresh();
285
-          break;
286
-        default:
287
-          console.warn(`未知按钮操作: ${name}`);
288
-      }
160
+  }
161
+
162
+  /**
163
+   * 刷新表格数据
164
+   */
165
+  refresh(): void {
166
+    if (this.tabulatorGrid) {
167
+      this.headerButtons[1].loading = true;
168
+      this.tabulatorGrid.refresh();
169
+      // 简单延迟后清除loading状态
170
+      setTimeout(() => {
171
+        this.headerButtons[1].loading = false;
172
+      }, 500);
289 173
     }
290
-   /**
291
-    * 刷新表格 - 重新加载数据
292
-    */
293
-   refresh(): void {
294
-     if (this.tabulator) {
295
-       this.headerButtons[1].loading = true;
296
-       this.tabulator.replaceData();
297
-       // 简单延迟后清除loading状态
298
-       setTimeout(() => {
299
-         this.headerButtons[1].loading = false;
300
-       }, 500);
301
-     }
302
-   }
303
-  
174
+  }
175
+
304 176
   /**
305 177
    * 重置筛选和排序
306 178
    */
307 179
   reset(): void {
308
-    if (this.tabulator) {
309
-      this.tabulator.clearFilter();
310
-      this.tabulator.clearSort();
180
+    if (this.tabulatorGrid) {
181
+      this.tabulatorGrid.reset();
311 182
     }
312 183
   }
313 184
 
@@ -326,11 +197,34 @@ export class ServiceRegisterConfigComponent implements AfterViewInit {
326 197
   }
327 198
 
328 199
   /**
329
-   * 导出数据
200
+   * 导出数据为 CSV
330 201
    */
331 202
   exportData(): void {
332
-    if (this.tabulator) {
333
-      this.tabulator.download('csv', 'config-meta-data.csv');
203
+    if (this.tabulatorGrid) {
204
+      this.tabulatorGrid.exportToCSV('config-meta-data.csv');
334 205
     }
335 206
   }
207
+
208
+  /**
209
+   * 处理表格数据加载完成事件
210
+   */
211
+  onDataLoaded(response: any): void {
212
+    console.log('✅ 表格数据加载完成:', {
213
+      last_page: response?.last_page,
214
+      data_count: response?.data?.length,
215
+      data_sample: response?.data?.slice(0, 2)
216
+    });
217
+  }
218
+
219
+  /**
220
+   * 处理 AJAX 错误事件
221
+   */
222
+  onAjaxError(error: any): void {
223
+    console.error('❌ 表格 AJAX 错误:', error);
224
+    console.error('错误详情:', {
225
+      message: error?.message,
226
+      status: error?.status,
227
+      url: error?.url
228
+    });
229
+  }
336 230
 }

+ 165
- 0
src/app/services/config-meta.service.spec.ts 查看文件

@@ -0,0 +1,165 @@
1
+import { TestBed } from '@angular/core/testing';
2
+import { provideHttpClient } from '@angular/common/http';
3
+import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
4
+import { ConfigMetaService } from './config-meta.service';
5
+import { ConfigService } from 'base-core';
6
+
7
+// Mock ConfigService
8
+class MockConfigService {
9
+  apiBaseUrl = 'http://localhost:8080';
10
+  useMockData = true; // 使用模拟数据以便测试
11
+}
12
+
13
+describe('ConfigMetaService', () => {
14
+  let service: ConfigMetaService;
15
+  let httpTestingController: HttpTestingController;
16
+  let mockConfigService: MockConfigService;
17
+
18
+  beforeEach(() => {
19
+    mockConfigService = new MockConfigService();
20
+    
21
+    TestBed.configureTestingModule({
22
+      providers: [
23
+        provideHttpClient(),
24
+        provideHttpClientTesting(),
25
+        ConfigMetaService,
26
+        { provide: ConfigService, useValue: mockConfigService }
27
+      ]
28
+    });
29
+    
30
+    service = TestBed.inject(ConfigMetaService);
31
+    httpTestingController = TestBed.inject(HttpTestingController);
32
+  });
33
+
34
+  afterEach(() => {
35
+    httpTestingController.verify();
36
+  });
37
+
38
+  it('should be created', () => {
39
+    expect(service).toBeTruthy();
40
+  });
41
+
42
+  describe('getListUrl', () => {
43
+    it('should return correct URL when useMockData is true', () => {
44
+      mockConfigService.useMockData = true;
45
+      expect(service.getListUrl()).toBe('http://localhost:8080/tabulator/config/meta/list');
46
+    });
47
+
48
+    it('should return correct URL when useMockData is false', () => {
49
+      mockConfigService.useMockData = false;
50
+      expect(service.getListUrl()).toBe('http://localhost:8080/tabulator/config/meta/list');
51
+    });
52
+  });
53
+
54
+  describe('listConfigMeta', () => {
55
+    it('should return mock data when useMockData is true', (done) => {
56
+      mockConfigService.useMockData = true;
57
+      
58
+      service.listConfigMeta().subscribe({
59
+        next: (data) => {
60
+          expect(data).toBeTruthy();
61
+          expect(Array.isArray(data)).toBeTrue();
62
+          // 模拟数据应该包含一些记录
63
+          expect(data.length).toBeGreaterThan(0);
64
+          done();
65
+        },
66
+        error: done.fail
67
+      });
68
+    });
69
+
70
+    it('should make HTTP request when useMockData is false', () => {
71
+      mockConfigService.useMockData = false;
72
+      const mockResponse = {
73
+        success: true,
74
+        data: [
75
+          { 
76
+            id: '1', 
77
+            configName: 'test', 
78
+            fieldName: 'testField', 
79
+            fieldType: 'string', 
80
+            yamlName: 'testYaml',
81
+            fieldDesc: 'test description',
82
+            creator: 'admin',
83
+            createdAt: '2024-01-01T00:00:00Z'
84
+          }
85
+        ]
86
+      };
87
+
88
+      service.listConfigMeta().subscribe(response => {
89
+        expect(response).toEqual(mockResponse.data);
90
+      });
91
+
92
+      const req = httpTestingController.expectOne('http://localhost:8080/tabulator/config/meta/list');
93
+      expect(req.request.method).toBe('POST');
94
+      req.flush(mockResponse);
95
+    });
96
+  });
97
+
98
+  describe('listForTabulator', () => {
99
+    it('should convert Tabulator params and return expected format', (done) => {
100
+      mockConfigService.useMockData = true;
101
+      const tabulatorParams = {
102
+        page: 1,
103
+        size: 20,
104
+        sort: [{ field: 'configName', dir: 'asc' }],
105
+        filter: [{ field: 'fieldType', type: '=', value: 'string' }]
106
+      };
107
+
108
+      service.listForTabulator(tabulatorParams).subscribe({
109
+        next: (response) => {
110
+          expect(response).toBeTruthy();
111
+          expect(response.last_page).toBeDefined();
112
+          expect(response.data).toBeDefined();
113
+          expect(Array.isArray(response.data)).toBeTrue();
114
+          done();
115
+        },
116
+        error: done.fail
117
+      });
118
+    });
119
+
120
+    it('should handle empty params', (done) => {
121
+      mockConfigService.useMockData = true;
122
+      const emptyParams = {};
123
+
124
+      service.listForTabulator(emptyParams).subscribe({
125
+        next: (response) => {
126
+          expect(response).toBeTruthy();
127
+          expect(response.last_page).toBeDefined();
128
+          expect(response.data).toBeDefined();
129
+          expect(Array.isArray(response.data)).toBeTrue();
130
+          done();
131
+        },
132
+        error: done.fail
133
+      });
134
+    });
135
+  });
136
+
137
+  describe('initConfigMeta', () => {
138
+    it('should return mock success when useMockData is true', (done) => {
139
+      mockConfigService.useMockData = true;
140
+      
141
+      service.initConfigMeta().subscribe({
142
+        next: (result) => {
143
+          expect(result).toBeTruthy();
144
+          expect(result.success).toBeTrue();
145
+          expect(result.data).toBe('模拟初始化成功');
146
+          done();
147
+        },
148
+        error: done.fail
149
+      });
150
+    });
151
+
152
+    it('should make HTTP POST request when useMockData is false', () => {
153
+      mockConfigService.useMockData = false;
154
+      const mockResponse = { success: true, data: '初始化成功' };
155
+
156
+      service.initConfigMeta().subscribe(response => {
157
+        expect(response).toEqual(mockResponse);
158
+      });
159
+
160
+      const req = httpTestingController.expectOne('http://localhost:8080/init/config/meta');
161
+      expect(req.request.method).toBe('POST');
162
+      req.flush(mockResponse);
163
+    });
164
+  });
165
+});

+ 66
- 16
src/app/services/config-meta.service.ts 查看文件

@@ -2,9 +2,10 @@ import { Injectable } from '@angular/core';
2 2
 import { HttpClient } from '@angular/common/http';
3 3
 import { Observable, of } from 'rxjs';
4 4
 import { map } from 'rxjs/operators';
5
-import { ConfigMeta, ConfigMetaQueryRequest, PaginatedQueryResult } from '../models/config-meta.model';
5
+import { ConfigMeta, ConfigMetaQueryRequest, PaginatedQueryResult, TabulatorSorter, TabulatorFilter } from '../models/config-meta.model';
6 6
 import { CONFIG_NAMES, FIELD_TYPES } from '../models/config-meta.constants';
7 7
 import { MockDataUtil } from '../utils/mock-data.util';
8
+import { TabulatorMapper } from '../utils/tabulator-mapper.util';
8 9
 import { ConfigService } from 'base-core';
9 10
 import { QueryResult } from './auth.service';
10 11
 
@@ -13,17 +14,19 @@ import { QueryResult } from './auth.service';
13 14
 })
14 15
 export class ConfigMetaService {
15 16
   private initApiPath = '/init/config/meta';
16
-  private listApiPath = '/tabulator/config/meta/list';
17
+  private listApiPath = '/config/meta/list';
17 18
 
18
-  constructor(
19
-    private http: HttpClient,
20
-    private config: ConfigService
21
-  ) {
22
-    console.debug('[配置元服务] 初始化,apiBaseUrl:', this.config.apiBaseUrl);
23
-    console.debug('[配置元服务] useMockData:', this.config.useMockData);
24
-    console.debug('[配置元服务] initApiPath:', this.initApiPath);
25
-    console.debug('[配置元服务] listApiPath:', this.listApiPath);
26
-  }
19
+   constructor(
20
+     private http: HttpClient,
21
+     private config: ConfigService
22
+   ) {
23
+     console.debug('[配置元服务] 初始化,apiBaseUrl:', this.config.apiBaseUrl);
24
+     console.debug('[配置元服务] useMockData:', this.config.useMockData);
25
+     console.debug('[配置元服务] initApiPath:', this.initApiPath);
26
+     console.debug('[配置元服务] listApiPath:', this.listApiPath);
27
+     console.debug('[配置元服务] 完整listUrl:', `${this.config.apiBaseUrl}${this.listApiPath}`);
28
+     console.debug('[配置元服务] ConfigService实例:', this.config);
29
+   }
27 30
 
28 31
   getInitUrl(): string {
29 32
     return `${this.config.apiBaseUrl}${this.initApiPath}`;
@@ -82,10 +85,17 @@ export class ConfigMetaService {
82 85
       return of(mockResult);
83 86
     }
84 87
     
85
-    const url = `${this.config.apiBaseUrl}${this.listApiPath}`;
86
-    console.debug(`[配置元服务] 调用真实API: ${url}`, request);
87
-    console.debug(`[配置元服务] 完整请求URL: ${url}`);
88
-    console.debug(`[配置元服务] 请求参数:`, JSON.stringify(request));
88
+     const url = `${this.config.apiBaseUrl}${this.listApiPath}`;
89
+     console.debug(`[配置元服务] 调用真实API: ${url}`, request);
90
+     console.debug(`[配置元服务] 完整请求URL: ${url}`);
91
+     console.debug(`[配置元服务] URL组成分析:`, {
92
+       apiBaseUrl: this.config.apiBaseUrl,
93
+       listApiPath: this.listApiPath,
94
+       combinedUrl: url,
95
+       isAbsoluteUrl: url.startsWith('http'),
96
+       useMockData: this.config.useMockData
97
+     });
98
+     console.debug(`[配置元服务] 请求参数:`, JSON.stringify(request));
89 99
     
90 100
     return this.http.post<PaginatedQueryResult<ConfigMeta[]>>(url, request)
91 101
       .pipe(
@@ -101,7 +111,7 @@ export class ConfigMetaService {
101 111
       );
102 112
   }
103 113
 
104
-  searchConfigMeta(configName?: string, fieldName?: string, yamlName?: string): Observable<ConfigMeta[]> {
114
+   searchConfigMeta(configName?: string, fieldName?: string, yamlName?: string): Observable<ConfigMeta[]> {
105 115
     if (this.config.useMockData) {
106 116
       return of(this.generateMockConfigMetaList());
107 117
     }
@@ -117,6 +127,46 @@ export class ConfigMetaService {
117 127
       );
118 128
   }
119 129
 
130
+  /**
131
+   * 为Tabulator表格提供数据加载服务
132
+   * @param params Tabulator格式的请求参数 {page, size, sort, filter}
133
+   * @returns Tabulator期望的响应格式 {last_page, data}
134
+   */
135
+  listForTabulator(params: any): Observable<any> {
136
+    console.debug('[配置元服务] Tabulator请求参数:', params);
137
+    
138
+    // 转换参数:Tabulator格式 → ConfigMetaQueryRequest
139
+    const request: ConfigMetaQueryRequest = TabulatorMapper.buildServerRequest(
140
+      params.page || 1,
141
+      params.size || 20,
142
+      params.sort || [],
143
+      params.filter || []
144
+    );
145
+    
146
+    console.debug('[配置元服务] 转换后的请求参数:', request);
147
+    
148
+    // 调用现有分页方法
149
+    return this.listConfigMetaPaginated(request).pipe(
150
+      map(response => {
151
+        console.debug('[配置元服务] 原始响应:', response);
152
+        
153
+        // 计算总页数
154
+        const pageSize = params.size || 20;
155
+        const totalCount = response.totalCount || 0;
156
+        const lastPage = Math.ceil(totalCount / pageSize) || 1;
157
+        
158
+        // 返回Tabulator期望的格式
159
+        const tabulatorResponse = {
160
+          last_page: lastPage,
161
+          data: response.data || []
162
+        };
163
+        
164
+        console.debug('[配置元服务] Tabulator格式响应:', tabulatorResponse);
165
+        return tabulatorResponse;
166
+      })
167
+    );
168
+  }
169
+
120 170
    private generateMockConfigMetaList(): ConfigMeta[] {
121 171
      return MockDataUtil.generateConfigMetaList(20);
122 172
    }

Loading…
取消
儲存