search-bar.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. (() => {
  2. /**
  3. * 统一管理搜索输入、模糊匹配与结果渲染
  4. */
  5. class SearchBar {
  6. constructor(options = {}) {
  7. this.input = options.input || null;
  8. this.clearButton = options.clearButton || null;
  9. this.fileList = options.fileList || null;
  10. this.emptyState = options.emptyState || null;
  11. this.getFiles = options.getFiles || (() => []);
  12. this.renderAll = options.renderAll || (() => {});
  13. this.createFileItem = options.createFileItem || (() => null);
  14. this.noResultMessage = options.noResultMessage || '没有找到匹配的文件';
  15. this.emptyStatePrimary = this.emptyState ? this.emptyState.querySelector('p') : null;
  16. this.defaultEmptyMessage = this.emptyStatePrimary ? this.emptyStatePrimary.textContent : '';
  17. this.bindEvents();
  18. this.toggleClearButton(this.getKeyword());
  19. }
  20. bindEvents() {
  21. if (this.input) {
  22. this.input.addEventListener('input', () => this.handleInput());
  23. }
  24. if (this.clearButton) {
  25. this.clearButton.addEventListener('click', () => this.clear());
  26. }
  27. }
  28. handleInput() {
  29. const keyword = this.getKeyword();
  30. this.toggleClearButton(keyword);
  31. if (!keyword) {
  32. this.restoreEmptyStateMessage();
  33. this.renderAll();
  34. return;
  35. }
  36. const filteredFiles = this.filterFiles(keyword);
  37. this.renderFilteredFiles(filteredFiles);
  38. }
  39. getKeyword() {
  40. if (!this.input) return '';
  41. return this.input.value.trim().toLowerCase();
  42. }
  43. filterFiles(keyword) {
  44. const files = this.getFiles() || [];
  45. return files.filter(file => {
  46. if (!file || !file.name) return false;
  47. return file.name.toLowerCase().includes(keyword);
  48. });
  49. }
  50. renderFilteredFiles(files) {
  51. if (!this.fileList) return;
  52. this.fileList.innerHTML = '';
  53. if (!files.length) {
  54. if (this.emptyState) {
  55. this.emptyState.classList.add('show');
  56. }
  57. if (this.emptyStatePrimary) {
  58. this.emptyStatePrimary.textContent = this.noResultMessage;
  59. }
  60. return;
  61. }
  62. if (this.emptyState) {
  63. this.emptyState.classList.remove('show');
  64. }
  65. files.forEach(file => {
  66. const item = this.createFileItem(file);
  67. if (item) {
  68. this.fileList.appendChild(item);
  69. }
  70. });
  71. }
  72. clear(options = {}) {
  73. const { shouldRender = true } = options;
  74. if (this.input) {
  75. this.input.value = '';
  76. }
  77. this.toggleClearButton('');
  78. this.restoreEmptyStateMessage();
  79. if (shouldRender) {
  80. this.renderAll();
  81. }
  82. }
  83. toggleClearButton(keyword) {
  84. if (!this.clearButton) return;
  85. this.clearButton.style.display = keyword ? 'flex' : 'none';
  86. }
  87. restoreEmptyStateMessage() {
  88. if (this.emptyStatePrimary) {
  89. this.emptyStatePrimary.textContent = this.defaultEmptyMessage;
  90. }
  91. }
  92. }
  93. window.SearchBar = SearchBar;
  94. })();