• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.95
/src/pagination/pagination.component.ts
1
import { NgTemplateOutlet } from '@angular/common';
2
import {
3
    ChangeDetectionStrategy,
4
    Component,
5
    TemplateRef,
6
    numberAttribute,
7
    inject,
8
    Signal,
9
    input,
10
    output,
11
    effect,
12
    computed,
13
    signal,
14
    model,
15
    WritableSignal,
16
    ModelSignal
17
} from '@angular/core';
18
import { FormsModule } from '@angular/forms';
19
import { ThyIcon } from 'ngx-tethys/icon';
20
import { ThySelect } from 'ngx-tethys/select';
1✔
21
import { ThyEnterDirective, ThyOption } from 'ngx-tethys/shared';
UNCOV
22
import { coerceBooleanProperty, isTemplateRef, ThyBooleanInput } from 'ngx-tethys/util';
×
UNCOV
23
import { ThyPaginationConfigModel } from './pagination.class';
×
UNCOV
24
import { PaginationDefaultConfig, THY_PAGINATION_CONFIG, DEFAULT_RANGE_COUNT } from './pagination.config';
×
UNCOV
25
import { PaginationPerPageFormat, PaginationTotalCountFormat } from './pagination.pipe';
×
UNCOV
26
import { injectLocale, ThyI18nLocale, ThyPaginationLocale } from 'ngx-tethys/i18n';
×
UNCOV
27
import { isString, isArray, isBoolean } from 'ngx-tethys/util';
×
UNCOV
28
/**
×
UNCOV
29
 * 分页组件,当数据量过多时,使用分页分解数据。
×
UNCOV
30
 * @name thy-pagination
×
UNCOV
31
 * @order 10
×
UNCOV
32
 */
×
UNCOV
33
@Component({
×
UNCOV
34
    selector: 'thy-pagination',
×
UNCOV
35
    templateUrl: './pagination.component.html',
×
UNCOV
36
    changeDetection: ChangeDetectionStrategy.OnPush,
×
UNCOV
37
    imports: [
×
UNCOV
38
        NgTemplateOutlet,
×
UNCOV
39
        ThySelect,
×
UNCOV
40
        FormsModule,
×
UNCOV
41
        ThyOption,
×
UNCOV
42
        ThyIcon,
×
UNCOV
43
        ThyEnterDirective,
×
UNCOV
44
        PaginationTotalCountFormat,
×
UNCOV
45
        PaginationPerPageFormat
×
UNCOV
46
    ],
×
UNCOV
47
    host: {
×
UNCOV
48
        class: 'thy-pagination',
×
UNCOV
49
        '[class.thy-pagination-sm]': 'thySize() === "sm"',
×
UNCOV
50
        '[class.thy-pagination-md]': 'thySize() === "md"',
×
51
        '[class.thy-pagination-lg]': 'thySize() === "lg"',
52
        '[class.thy-pagination-has-total]': 'thyShowTotal()'
53
    }
54
})
55
export class ThyPagination {
UNCOV
56
    private paginationConfig = inject(THY_PAGINATION_CONFIG, { optional: true })!;
×
UNCOV
57

×
58
    allLocale: Signal<ThyI18nLocale> = injectLocale();
UNCOV
59

×
60
    paginationLocale: Signal<ThyPaginationLocale> = injectLocale('pagination');
×
61

UNCOV
62
    isTemplateRef = isTemplateRef;
×
UNCOV
63

×
64
    /**
UNCOV
65
     * 设置当前页,支持双向绑定
×
66
     * @default 1
×
67
     */
UNCOV
68
    readonly thyPageIndex = input<number, unknown>(undefined, { transform: numberAttribute });
×
UNCOV
69

×
70
    /**
UNCOV
71
     * 每页条目数量
×
UNCOV
72
     * @default 20
×
73
     */
UNCOV
74
    readonly thyPageSize = input<number, unknown>(undefined, { transform: numberAttribute });
×
UNCOV
75

×
76
    /**
UNCOV
77
     * 数据总数
×
78
     */
×
79
    readonly thyTotal = input<number, unknown>(undefined, { transform: numberAttribute });
UNCOV
80

×
81
    /**
UNCOV
82
     * 自定义分页页码,设置自定义分页页码后将不根据 Total 和 PageSize 来自动计算页码,完全以传入的页码为准
×
UNCOV
83
     * @type number[]
×
UNCOV
84
     */
×
85
    readonly thyCustomPages = input<number[]>();
86

87
    /**
×
88
     * 是否禁用
89
     */
UNCOV
90
    readonly thyDisabled = input<boolean, ThyBooleanInput>(false, { transform: coerceBooleanProperty });
×
UNCOV
91

×
UNCOV
92
    /**
×
UNCOV
93
     * 是否显示快速跳转
×
94
     * @default false
95
     */
UNCOV
96
    readonly thyShowQuickJumper = input<boolean, ThyBooleanInput>(undefined, { transform: coerceBooleanProperty });
×
97

UNCOV
98
    /**
×
99
     * 设置是否显示总页数信息
UNCOV
100
     * @default true
×
UNCOV
101
     */
×
UNCOV
102
    readonly thyShowTotalPageCount = input<boolean, ThyBooleanInput>(undefined, { transform: coerceBooleanProperty });
×
UNCOV
103

×
UNCOV
104
    /**
×
UNCOV
105
     * 设置分页组件的大小
×
UNCOV
106
     * @type sm | md | lg
×
107
     * @default md
108
     */
UNCOV
109
    readonly thySize = input<'sm' | 'md' | 'lg'>('md', { alias: 'thySize' });
×
UNCOV
110

×
UNCOV
111
    /**
×
UNCOV
112
     * 设置最大显示数量,超出最大显示数后会自动进行分割显示
×
UNCOV
113
     * @default 9
×
UNCOV
114
     */
×
UNCOV
115
    readonly thyMaxCount = input<number, unknown>(undefined, { transform: numberAttribute });
×
UNCOV
116

×
117
    /**
UNCOV
118
     * 设置边缘显示数量
×
UNCOV
119
     * @default 2
×
UNCOV
120
     */
×
UNCOV
121
    readonly thyMarginalCount = input<number, unknown>(undefined, { transform: numberAttribute });
×
122

UNCOV
123
    /**
×
124
     * 设置中间区域显示数量
×
125
     * @default 5
UNCOV
126
     */
×
UNCOV
127
    readonly thyRangeCount = input<number, unknown>(undefined, { transform: numberAttribute });
×
128

129
    /**
UNCOV
130
     * 是否显示分页大小选择器
×
UNCOV
131
     * @default false
×
132
     */
UNCOV
133
    readonly thyShowSizeChanger = input<boolean, ThyBooleanInput>(undefined, { transform: coerceBooleanProperty });
×
UNCOV
134

×
135
    /**
136
     * @type number[]
UNCOV
137
     */
×
UNCOV
138
    readonly thyPageSizeOptions = input<number[]>(undefined);
×
139

UNCOV
140
    /**
×
UNCOV
141
     * 只有一页时是否隐藏分页器
×
142
     * @default false
UNCOV
143
     */
×
144
    readonly thyHideOnSinglePage = input<boolean, ThyBooleanInput>(undefined, { transform: coerceBooleanProperty });
145

UNCOV
146
    /**
×
UNCOV
147
     * 分页器单位
×
148
     * @default 条
149
     */
UNCOV
150
    readonly thyUnit = input<string>();
×
151

UNCOV
152
    /**
×
UNCOV
153
     * 是否显示范围和total
×
UNCOV
154
     * @default false
×
UNCOV
155
     */
×
UNCOV
156

×
UNCOV
157
    readonly thyShowTotal = input<boolean | TemplateRef<{ $implicit: number; range: { from: number; to: number } }>>(false);
×
158

UNCOV
159
    /**
×
UNCOV
160
     * 页码改变的回调
×
UNCOV
161
     */
×
UNCOV
162
    readonly thyPageIndexChange = output<number>();
×
163

164
    /**
UNCOV
165
     * 与Bootstrap pagination 兼容,后续版本会进行删除,参数保持与 bootstrap 一致
×
UNCOV
166
     */
×
UNCOV
167
    readonly thyPageChanged = output<{ page: number }>();
×
UNCOV
168

×
UNCOV
169
    readonly thyPageSizeChanged = output<number>();
×
170

171
    public currentPageIndex = signal(1);
UNCOV
172

×
UNCOV
173
    public currentPageSize: WritableSignal<number> = signal(null);
×
UNCOV
174

×
175
    public selectedPageSize: ModelSignal<number> = model();
176

177
    public firstIndex = 1;
×
178

UNCOV
179
    computedConfig: Signal<ThyPaginationConfigModel> = computed(() => {
×
UNCOV
180
        const result: ThyPaginationConfigModel = Object.assign(
×
181
            {},
182
            PaginationDefaultConfig,
183
            {
184
                firstText: this.paginationLocale().firstPage,
UNCOV
185
                lastText: this.paginationLocale().lastPage,
×
UNCOV
186
                totalPagesFormat: this.paginationLocale().totalCount,
×
187
                unit: this.paginationLocale().defaultUnit
188
            },
UNCOV
189
            this.paginationConfig.main
×
190
        );
191

UNCOV
192
        if (isBoolean(this.thyShowQuickJumper())) {
×
UNCOV
193
            result.showQuickJumper = this.thyShowQuickJumper();
×
194
        }
195

UNCOV
196
        if (isBoolean(this.thyShowTotalPageCount())) {
×
197
            result.showTotalPageCount = this.thyShowTotalPageCount();
×
198
        }
UNCOV
199

×
UNCOV
200
        if (this.thyCustomPages() && isArray(this.thyCustomPages())) {
×
201
            result.showTotalPageCount = false;
202
        }
UNCOV
203

×
UNCOV
204
        if (Number.isInteger(this.thyMaxCount())) {
×
UNCOV
205
            result.maxCount = this.thyMaxCount();
×
206
        }
UNCOV
207

×
208
        if (isString(this.thyUnit()) && this.thyUnit()) {
209
            result.unit = this.thyUnit();
UNCOV
210
        }
×
UNCOV
211

×
UNCOV
212
        if (isBoolean(this.thyShowSizeChanger())) {
×
213
            result.showSizeChanger = this.thyShowSizeChanger();
214
        }
1✔
215

1✔
216
        if (this.thyPageSizeOptions() && isArray(this.thyPageSizeOptions())) {
217
            result.pageSizeOptions = this.thyPageSizeOptions();
218
        }
219

220
        if (Number.isInteger(this.thyRangeCount())) {
221
            result.rangeCount = this.thyRangeCount();
222
        }
223

224
        return result;
225
    });
226

227
    marginalCount = computed(() => {
228
        if (!this.thyMarginalCount()) {
229
            return this.computedConfig().rangeCount <= DEFAULT_RANGE_COUNT ? 1 : 2;
230
        } else {
231
            return this.thyMarginalCount();
232
        }
233
    });
234

235
    computedPageCount = computed(() => {
236
        let pageCount = null;
237
        if (this.thyCustomPages() && this.thyCustomPages().length > 0) {
238
            pageCount = this.thyCustomPages()[this.thyCustomPages().length - 1];
1✔
239
        } else {
240
            pageCount = this.currentPageSize() < 1 ? 1 : Math.ceil(this.thyTotal() / this.currentPageSize());
241
        }
242
        return Math.max(pageCount || 0, 1);
243
    });
244

245
    computedPages = computed(() => {
246
        const pageCount = this.computedPageCount();
247
        const pageIndex = this.currentPageIndex();
248
        const config = this.computedConfig();
249

250
        if (this.thyCustomPages() && this.thyCustomPages().length > 0) {
251
            return this.thyCustomPages().map(page => {
252
                return { index: page, text: page.toString(), active: page === +pageIndex };
253
            });
254
        }
255

256
        let pages = [];
257
        const marginalCount = this.marginalCount();
258
        const rangeCount = config.rangeCount;
259
        const maxCount = config.maxCount;
260
        const isMaxSized = pageCount > maxCount;
261
        if (isMaxSized) {
262
            const beforePages = [];
263
            const afterPages = [];
264

265
            // mainPages
266
            let start = Math.ceil(Math.max(marginalCount + 1, pageIndex - (rangeCount - 1) / 2));
267
            let end = Math.ceil(Math.min(pageIndex + (rangeCount - 1) / 2, pageCount - marginalCount));
268
            if (pageIndex - 1 < marginalCount) {
269
                end = rangeCount;
270
            }
271
            if (pageCount - pageIndex <= marginalCount) {
272
                start = pageCount - rangeCount + 1;
273
            }
274

275
            for (let i = start; i <= end; i++) {
276
                pages.push({ index: i, text: i.toString(), active: i === +pageIndex });
277
            }
278

279
            // beforePages
280
            for (let i = 1; i <= marginalCount; i++) {
281
                beforePages.push(this.makePage(i, i.toString(), i === pageIndex));
282
            }
283

284
            if (pageIndex - Math.ceil(rangeCount / 2) > this.firstIndex && marginalCount + 1 < start) {
285
                beforePages.push(this.makePage(Math.ceil((marginalCount + start) / 2), '···', null));
286
            }
287

288
            // afterPages
289
            if (pageIndex + Math.ceil(rangeCount / 2) < pageCount && pageCount - marginalCount > end) {
290
                afterPages.push(this.makePage(Math.ceil((pageCount - marginalCount + 1 + end) / 2), '···', null));
291
            }
292
            for (let i = pageCount - marginalCount + 1; i <= pageCount; i++) {
293
                afterPages.push(this.makePage(i, i.toString(), i === pageIndex));
294
            }
295

296
            pages = [...beforePages, ...pages, ...afterPages];
297
        } else {
298
            for (let i = 1; i <= pageCount; i++) {
299
                pages.push({ index: i, text: i.toString(), active: i === +pageIndex });
300
            }
301
        }
302
        return pages;
303
    });
304

305
    computedRange = computed(() => {
306
        const pageIndex = this.currentPageIndex();
307
        const pageSize = this.currentPageSize();
308
        const total = this.thyTotal();
309
        const toPageSize = pageIndex * pageSize;
310
        return { from: (pageIndex - 1) * pageSize + 1, to: toPageSize > total ? total : toPageSize };
311
    });
312

313
    constructor() {
314
        effect(() => {
315
            const pageIndex = this.thyPageIndex();
316
            if (Number.isInteger(pageIndex)) {
317
                this.setPageIndex(pageIndex);
318
            }
319
        });
320

321
        effect(() => {
322
            let pageSize = this.thyPageSize();
323

324
            if (Number.isInteger(pageSize)) {
325
                this.currentPageSize.set(pageSize);
326
                this.selectedPageSize.set(pageSize);
327
            } else {
328
                const config = this.computedConfig();
329
                if (config.pageSizeOptions && config.pageSizeOptions.length > 0) {
330
                    pageSize = config.pageSizeOptions[0];
331
                } else {
332
                    pageSize = config.pageSize;
333
                }
334
                this.currentPageSize.set(pageSize);
335
                this.selectedPageSize.set(pageSize);
336
            }
337
        });
338
    }
339

340
    private setPageIndex(pageIndex: number) {
341
        pageIndex = pageIndex > this.computedPageCount() ? this.computedPageCount() : pageIndex || 1;
342
        this.currentPageIndex.set(pageIndex);
343
    }
344

345
    private makePage(index: number, text: string, active: boolean): { index: number; text: string; active: boolean } {
346
        return { index, text, active };
347
    }
348

349
    private pageChange(pageIndex: number) {
350
        this.thyPageIndexChange.emit(pageIndex);
351
        this.thyPageChanged.emit({ page: pageIndex });
352
    }
353

354
    selectPage(pageIndex: number) {
355
        if (this.thyDisabled() || pageIndex === this.firstIndex - 1 || pageIndex === this.computedPageCount() + 1) {
356
            return;
357
        }
358
        this.setPageIndex(pageIndex);
359
        this.pageChange(this.currentPageIndex());
360
    }
361

362
    jumpPage(input: HTMLInputElement) {
363
        const pageIndex = +input.value;
364
        if (Number.isInteger(pageIndex)) {
365
            this.selectPage(pageIndex);
366
        }
367
        input.value = '';
368
    }
369

370
    onPageSizeChange(event: number) {
371
        this.currentPageSize.set(event);
372
        this.setPageIndex(event);
373
        this.thyPageSizeChanged.emit(event);
374
    }
375
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc