aifastdb 3.7.6 → 3.8.6-mac.0

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.
@@ -22,172 +22,172 @@ function renderCrudTable(config) {
22
22
  const totalPages = Math.ceil(total / pageSize);
23
23
  const tableFields = fields.filter(f => f.showInTable !== false);
24
24
  const formFields = fields.filter(f => f.showInForm !== false);
25
- return `
25
+ return `
26
26
  <div class="crud-table" id="crud-${id}" data-config='${JSON.stringify({
27
27
  id,
28
28
  pageSize,
29
29
  currentPage,
30
30
  iconOptions,
31
31
  deleteConfirmMessage,
32
- })}'>
33
- <!-- 头部 -->
34
- <div class="crud-header">
35
- <div class="crud-title">
36
- ${iconName ? (0, icons_1.icon)(iconName) : ''}
37
- <h1>${title}</h1>
38
- <span class="crud-count">共 ${total} 条记录</span>
39
- </div>
40
- <div class="crud-actions">
41
- ${canCreate ? `
42
- <button class="btn btn-success" onclick="CrudTable.showCreate('${id}')">
43
- ${(0, icons_1.icon)('plus')}
44
- <span>添加</span>
45
- </button>
46
- ` : ''}
47
- ${canRefresh ? `
48
- <button class="btn btn-primary" onclick="CrudTable.refresh('${id}')">
49
- ${(0, icons_1.icon)('refresh')}
50
- <span>刷新</span>
51
- </button>
52
- ` : ''}
53
- </div>
54
- </div>
55
-
56
- <!-- 搜索栏 -->
57
- ${canSearch ? `
58
- <div class="crud-search">
59
- <form onsubmit="CrudTable.search('${id}', event)">
60
- <div class="search-box">
61
- ${(0, icons_1.icon)('search')}
62
- <input
63
- type="text"
64
- id="crud-${id}-search"
65
- value="${escapeHtml(searchValue)}"
66
- placeholder="${searchPlaceholder}"
67
- >
68
- </div>
69
- <button type="submit" class="btn btn-secondary">搜索</button>
70
- </form>
71
- </div>
72
- ` : ''}
73
-
74
- <!-- 表格 -->
75
- <div class="card">
76
- <div class="table-container">
77
- <table class="table crud-data-table" id="crud-${id}-table">
78
- <thead>
79
- <tr>
80
- ${tableFields.map(field => `
81
- <th style="${field.width ? `width: ${field.width}` : ''}">${field.label}</th>
82
- `).join('')}
83
- ${(canEdit || canDelete || extraActions) ? `
84
- <th class="actions-column">操作</th>
85
- ` : ''}
86
- </tr>
87
- </thead>
88
- <tbody id="crud-${id}-tbody">
89
- ${data.length > 0 ? data.map(record => renderTableRow(id, record, tableFields, canEdit, canDelete, extraActions)).join('') : `
90
- <tr>
91
- <td colspan="${tableFields.length + (canEdit || canDelete || extraActions ? 1 : 0)}">
92
- <div class="empty-state">
93
- ${(0, icons_1.icon)('empty')}
94
- <h3>暂无数据</h3>
95
- </div>
96
- </td>
97
- </tr>
98
- `}
99
- </tbody>
100
- </table>
101
- </div>
102
-
103
- <!-- 分页 -->
104
- ${totalPages > 1 ? `
105
- <div class="crud-pagination">
106
- <span class="pagination-info">第 ${currentPage} / ${totalPages} 页</span>
107
- <div class="pagination">
108
- <button
109
- class="pagination-btn"
110
- onclick="CrudTable.goToPage('${id}', ${currentPage - 1})"
111
- ${currentPage <= 1 ? 'disabled' : ''}
112
- >
113
- ${(0, icons_1.icon)('chevronLeft')}
114
- </button>
115
- ${generatePaginationButtons(id, currentPage, totalPages)}
116
- <button
117
- class="pagination-btn"
118
- onclick="CrudTable.goToPage('${id}', ${currentPage + 1})"
119
- ${currentPage >= totalPages ? 'disabled' : ''}
120
- >
121
- ${(0, icons_1.icon)('chevronRight')}
122
- </button>
123
- </div>
124
- </div>
125
- ` : ''}
126
- </div>
127
-
128
- <!-- 创建模态框 -->
129
- <div class="modal-overlay" id="crud-${id}-create-modal">
130
- <div class="modal">
131
- <div class="modal-header">
132
- <h2>${(0, icons_1.icon)('plus')} 添加${title.replace('管理', '')}</h2>
133
- <button class="modal-close" onclick="CrudTable.closeModal('crud-${id}-create-modal')">
134
- ${(0, icons_1.icon)('close')}
135
- </button>
136
- </div>
137
- <form id="crud-${id}-create-form" onsubmit="CrudTable.create('${id}', event)">
138
- <div class="modal-body">
139
- <div id="crud-${id}-create-error" class="alert alert-danger" style="display: none;"></div>
140
- ${formFields.map(field => renderFormField(id, 'create', field, iconOptions)).join('')}
141
- </div>
142
- <div class="modal-footer">
143
- <button type="button" class="btn btn-secondary" onclick="CrudTable.closeModal('crud-${id}-create-modal')">
144
- 取消
145
- </button>
146
- <button type="submit" class="btn btn-success">
147
- ${(0, icons_1.icon)('plus')}
148
- <span>创建</span>
149
- </button>
150
- </div>
151
- </form>
152
- </div>
153
- </div>
154
-
155
- <!-- 编辑模态框 -->
156
- <div class="modal-overlay" id="crud-${id}-edit-modal">
157
- <div class="modal">
158
- <div class="modal-header">
159
- <h2>${(0, icons_1.icon)('edit')} 编辑${title.replace('管理', '')}</h2>
160
- <button class="modal-close" onclick="CrudTable.closeModal('crud-${id}-edit-modal')">
161
- ${(0, icons_1.icon)('close')}
162
- </button>
163
- </div>
164
- <form id="crud-${id}-edit-form" onsubmit="CrudTable.update('${id}', event)">
165
- <input type="hidden" name="id" id="crud-${id}-edit-id">
166
- <div class="modal-body">
167
- <div id="crud-${id}-edit-error" class="alert alert-danger" style="display: none;"></div>
168
- ${formFields.map(field => renderFormField(id, 'edit', field, iconOptions)).join('')}
169
- </div>
170
- <div class="modal-footer">
171
- <button type="button" class="btn btn-secondary" onclick="CrudTable.closeModal('crud-${id}-edit-modal')">
172
- 取消
173
- </button>
174
- <button type="submit" class="btn btn-primary">
175
- ${(0, icons_1.icon)('save')}
176
- <span>保存</span>
177
- </button>
178
- </div>
179
- </form>
180
- </div>
181
- </div>
182
- </div>
32
+ })}'>
33
+ <!-- 头部 -->
34
+ <div class="crud-header">
35
+ <div class="crud-title">
36
+ ${iconName ? (0, icons_1.icon)(iconName) : ''}
37
+ <h1>${title}</h1>
38
+ <span class="crud-count">共 ${total} 条记录</span>
39
+ </div>
40
+ <div class="crud-actions">
41
+ ${canCreate ? `
42
+ <button class="btn btn-success" onclick="CrudTable.showCreate('${id}')">
43
+ ${(0, icons_1.icon)('plus')}
44
+ <span>添加</span>
45
+ </button>
46
+ ` : ''}
47
+ ${canRefresh ? `
48
+ <button class="btn btn-primary" onclick="CrudTable.refresh('${id}')">
49
+ ${(0, icons_1.icon)('refresh')}
50
+ <span>刷新</span>
51
+ </button>
52
+ ` : ''}
53
+ </div>
54
+ </div>
55
+
56
+ <!-- 搜索栏 -->
57
+ ${canSearch ? `
58
+ <div class="crud-search">
59
+ <form onsubmit="CrudTable.search('${id}', event)">
60
+ <div class="search-box">
61
+ ${(0, icons_1.icon)('search')}
62
+ <input
63
+ type="text"
64
+ id="crud-${id}-search"
65
+ value="${escapeHtml(searchValue)}"
66
+ placeholder="${searchPlaceholder}"
67
+ >
68
+ </div>
69
+ <button type="submit" class="btn btn-secondary">搜索</button>
70
+ </form>
71
+ </div>
72
+ ` : ''}
73
+
74
+ <!-- 表格 -->
75
+ <div class="card">
76
+ <div class="table-container">
77
+ <table class="table crud-data-table" id="crud-${id}-table">
78
+ <thead>
79
+ <tr>
80
+ ${tableFields.map(field => `
81
+ <th style="${field.width ? `width: ${field.width}` : ''}">${field.label}</th>
82
+ `).join('')}
83
+ ${(canEdit || canDelete || extraActions) ? `
84
+ <th class="actions-column">操作</th>
85
+ ` : ''}
86
+ </tr>
87
+ </thead>
88
+ <tbody id="crud-${id}-tbody">
89
+ ${data.length > 0 ? data.map(record => renderTableRow(id, record, tableFields, canEdit, canDelete, extraActions)).join('') : `
90
+ <tr>
91
+ <td colspan="${tableFields.length + (canEdit || canDelete || extraActions ? 1 : 0)}">
92
+ <div class="empty-state">
93
+ ${(0, icons_1.icon)('empty')}
94
+ <h3>暂无数据</h3>
95
+ </div>
96
+ </td>
97
+ </tr>
98
+ `}
99
+ </tbody>
100
+ </table>
101
+ </div>
102
+
103
+ <!-- 分页 -->
104
+ ${totalPages > 1 ? `
105
+ <div class="crud-pagination">
106
+ <span class="pagination-info">第 ${currentPage} / ${totalPages} 页</span>
107
+ <div class="pagination">
108
+ <button
109
+ class="pagination-btn"
110
+ onclick="CrudTable.goToPage('${id}', ${currentPage - 1})"
111
+ ${currentPage <= 1 ? 'disabled' : ''}
112
+ >
113
+ ${(0, icons_1.icon)('chevronLeft')}
114
+ </button>
115
+ ${generatePaginationButtons(id, currentPage, totalPages)}
116
+ <button
117
+ class="pagination-btn"
118
+ onclick="CrudTable.goToPage('${id}', ${currentPage + 1})"
119
+ ${currentPage >= totalPages ? 'disabled' : ''}
120
+ >
121
+ ${(0, icons_1.icon)('chevronRight')}
122
+ </button>
123
+ </div>
124
+ </div>
125
+ ` : ''}
126
+ </div>
127
+
128
+ <!-- 创建模态框 -->
129
+ <div class="modal-overlay" id="crud-${id}-create-modal">
130
+ <div class="modal">
131
+ <div class="modal-header">
132
+ <h2>${(0, icons_1.icon)('plus')} 添加${title.replace('管理', '')}</h2>
133
+ <button class="modal-close" onclick="CrudTable.closeModal('crud-${id}-create-modal')">
134
+ ${(0, icons_1.icon)('close')}
135
+ </button>
136
+ </div>
137
+ <form id="crud-${id}-create-form" onsubmit="CrudTable.create('${id}', event)">
138
+ <div class="modal-body">
139
+ <div id="crud-${id}-create-error" class="alert alert-danger" style="display: none;"></div>
140
+ ${formFields.map(field => renderFormField(id, 'create', field, iconOptions)).join('')}
141
+ </div>
142
+ <div class="modal-footer">
143
+ <button type="button" class="btn btn-secondary" onclick="CrudTable.closeModal('crud-${id}-create-modal')">
144
+ 取消
145
+ </button>
146
+ <button type="submit" class="btn btn-success">
147
+ ${(0, icons_1.icon)('plus')}
148
+ <span>创建</span>
149
+ </button>
150
+ </div>
151
+ </form>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- 编辑模态框 -->
156
+ <div class="modal-overlay" id="crud-${id}-edit-modal">
157
+ <div class="modal">
158
+ <div class="modal-header">
159
+ <h2>${(0, icons_1.icon)('edit')} 编辑${title.replace('管理', '')}</h2>
160
+ <button class="modal-close" onclick="CrudTable.closeModal('crud-${id}-edit-modal')">
161
+ ${(0, icons_1.icon)('close')}
162
+ </button>
163
+ </div>
164
+ <form id="crud-${id}-edit-form" onsubmit="CrudTable.update('${id}', event)">
165
+ <input type="hidden" name="id" id="crud-${id}-edit-id">
166
+ <div class="modal-body">
167
+ <div id="crud-${id}-edit-error" class="alert alert-danger" style="display: none;"></div>
168
+ ${formFields.map(field => renderFormField(id, 'edit', field, iconOptions)).join('')}
169
+ </div>
170
+ <div class="modal-footer">
171
+ <button type="button" class="btn btn-secondary" onclick="CrudTable.closeModal('crud-${id}-edit-modal')">
172
+ 取消
173
+ </button>
174
+ <button type="submit" class="btn btn-primary">
175
+ ${(0, icons_1.icon)('save')}
176
+ <span>保存</span>
177
+ </button>
178
+ </div>
179
+ </form>
180
+ </div>
181
+ </div>
182
+ </div>
183
183
  `;
184
184
  }
185
185
  /**
186
186
  * 渲染表格行
187
187
  */
188
188
  function renderTableRow(tableId, record, fields, canEdit, canDelete, extraActions) {
189
- return `
190
- <tr data-id="${record.id}">
189
+ return `
190
+ <tr data-id="${record.id}">
191
191
  ${fields.map(field => {
192
192
  const value = record[field.key];
193
193
  let displayValue;
@@ -211,25 +211,25 @@ function renderTableRow(tableId, record, fields, canEdit, canDelete, extraAction
211
211
  displayValue = escapeHtml(String(value || '-'));
212
212
  }
213
213
  return `<td class="${field.width ? 'truncate' : ''}" style="${field.width ? `max-width: ${field.width}` : ''}">${displayValue}</td>`;
214
- }).join('')}
215
- ${(canEdit || canDelete || extraActions) ? `
216
- <td class="actions-cell">
217
- <div class="flex gap-2">
218
- ${extraActions}
219
- ${canEdit ? `
220
- <button class="btn btn-secondary btn-sm btn-icon" onclick="CrudTable.showEdit('${tableId}', '${record.id}')" title="编辑">
221
- ${(0, icons_1.icon)('edit')}
222
- </button>
223
- ` : ''}
224
- ${canDelete ? `
225
- <button class="btn btn-danger btn-sm btn-icon" onclick="CrudTable.confirmDelete('${tableId}', '${record.id}')" title="删除">
226
- ${(0, icons_1.icon)('trash')}
227
- </button>
228
- ` : ''}
229
- </div>
230
- </td>
231
- ` : ''}
232
- </tr>
214
+ }).join('')}
215
+ ${(canEdit || canDelete || extraActions) ? `
216
+ <td class="actions-cell">
217
+ <div class="flex gap-2">
218
+ ${extraActions}
219
+ ${canEdit ? `
220
+ <button class="btn btn-secondary btn-sm btn-icon" onclick="CrudTable.showEdit('${tableId}', '${record.id}')" title="编辑">
221
+ ${(0, icons_1.icon)('edit')}
222
+ </button>
223
+ ` : ''}
224
+ ${canDelete ? `
225
+ <button class="btn btn-danger btn-sm btn-icon" onclick="CrudTable.confirmDelete('${tableId}', '${record.id}')" title="删除">
226
+ ${(0, icons_1.icon)('trash')}
227
+ </button>
228
+ ` : ''}
229
+ </div>
230
+ </td>
231
+ ` : ''}
232
+ </tr>
233
233
  `;
234
234
  }
235
235
  /**
@@ -243,111 +243,111 @@ function renderFormField(tableId, formType, field, iconOptions) {
243
243
  let inputHtml;
244
244
  switch (field.type) {
245
245
  case 'select':
246
- inputHtml = `
247
- <select class="form-select" id="${inputId}" name="${inputName}" ${isRequired}>
248
- ${field.options?.map(opt => `
249
- <option value="${opt.value}">${opt.label}</option>
250
- `).join('')}
251
- </select>
246
+ inputHtml = `
247
+ <select class="form-select" id="${inputId}" name="${inputName}" ${isRequired}>
248
+ ${field.options?.map(opt => `
249
+ <option value="${opt.value}">${opt.label}</option>
250
+ `).join('')}
251
+ </select>
252
252
  `;
253
253
  break;
254
254
  case 'textarea':
255
- inputHtml = `
256
- <textarea
257
- class="form-textarea"
258
- id="${inputId}"
259
- name="${inputName}"
260
- placeholder="${placeholder}"
261
- ${isRequired}
262
- ></textarea>
255
+ inputHtml = `
256
+ <textarea
257
+ class="form-textarea"
258
+ id="${inputId}"
259
+ name="${inputName}"
260
+ placeholder="${placeholder}"
261
+ ${isRequired}
262
+ ></textarea>
263
263
  `;
264
264
  break;
265
265
  case 'icon':
266
- inputHtml = `
267
- <div class="icon-picker" id="${inputId}-picker">
268
- <input type="hidden" id="${inputId}" name="${inputName}" value="${iconOptions[0]}">
269
- <div class="icon-options">
270
- ${iconOptions.map((ico, idx) => `
271
- <button
272
- type="button"
273
- class="icon-option ${idx === 0 ? 'selected' : ''}"
274
- data-icon="${ico}"
275
- onclick="CrudTable.selectIcon('${inputId}', '${ico}', this)"
276
- >
277
- ${ico}
278
- </button>
279
- `).join('')}
280
- </div>
281
- </div>
266
+ inputHtml = `
267
+ <div class="icon-picker" id="${inputId}-picker">
268
+ <input type="hidden" id="${inputId}" name="${inputName}" value="${iconOptions[0]}">
269
+ <div class="icon-options">
270
+ ${iconOptions.map((ico, idx) => `
271
+ <button
272
+ type="button"
273
+ class="icon-option ${idx === 0 ? 'selected' : ''}"
274
+ data-icon="${ico}"
275
+ onclick="CrudTable.selectIcon('${inputId}', '${ico}', this)"
276
+ >
277
+ ${ico}
278
+ </button>
279
+ `).join('')}
280
+ </div>
281
+ </div>
282
282
  `;
283
283
  break;
284
284
  case 'number':
285
- inputHtml = `
286
- <input
287
- type="number"
288
- class="form-input"
289
- id="${inputId}"
290
- name="${inputName}"
291
- placeholder="${placeholder}"
292
- ${isRequired}
293
- >
285
+ inputHtml = `
286
+ <input
287
+ type="number"
288
+ class="form-input"
289
+ id="${inputId}"
290
+ name="${inputName}"
291
+ placeholder="${placeholder}"
292
+ ${isRequired}
293
+ >
294
294
  `;
295
295
  break;
296
296
  case 'email':
297
- inputHtml = `
298
- <input
299
- type="email"
300
- class="form-input"
301
- id="${inputId}"
302
- name="${inputName}"
303
- placeholder="${placeholder}"
304
- ${isRequired}
305
- >
297
+ inputHtml = `
298
+ <input
299
+ type="email"
300
+ class="form-input"
301
+ id="${inputId}"
302
+ name="${inputName}"
303
+ placeholder="${placeholder}"
304
+ ${isRequired}
305
+ >
306
306
  `;
307
307
  break;
308
308
  case 'password':
309
- inputHtml = `
310
- <input
311
- type="password"
312
- class="form-input"
313
- id="${inputId}"
314
- name="${inputName}"
315
- placeholder="${placeholder}"
316
- ${isRequired}
317
- >
309
+ inputHtml = `
310
+ <input
311
+ type="password"
312
+ class="form-input"
313
+ id="${inputId}"
314
+ name="${inputName}"
315
+ placeholder="${placeholder}"
316
+ ${isRequired}
317
+ >
318
318
  `;
319
319
  break;
320
320
  case 'date':
321
- inputHtml = `
322
- <input
323
- type="datetime-local"
324
- class="form-input"
325
- id="${inputId}"
326
- name="${inputName}"
327
- ${isRequired}
328
- >
321
+ inputHtml = `
322
+ <input
323
+ type="datetime-local"
324
+ class="form-input"
325
+ id="${inputId}"
326
+ name="${inputName}"
327
+ ${isRequired}
328
+ >
329
329
  `;
330
330
  break;
331
331
  default:
332
- inputHtml = `
333
- <input
334
- type="text"
335
- class="form-input"
336
- id="${inputId}"
337
- name="${inputName}"
338
- placeholder="${placeholder}"
339
- ${isRequired}
340
- >
332
+ inputHtml = `
333
+ <input
334
+ type="text"
335
+ class="form-input"
336
+ id="${inputId}"
337
+ name="${inputName}"
338
+ placeholder="${placeholder}"
339
+ ${isRequired}
340
+ >
341
341
  `;
342
342
  }
343
- return `
344
- <div class="form-group">
345
- <label class="form-label" for="${inputId}">
346
- ${field.label}
347
- ${field.required ? '<span class="required">*</span>' : ''}
348
- </label>
349
- ${inputHtml}
350
- </div>
343
+ return `
344
+ <div class="form-group">
345
+ <label class="form-label" for="${inputId}">
346
+ ${field.label}
347
+ ${field.required ? '<span class="required">*</span>' : ''}
348
+ </label>
349
+ ${inputHtml}
350
+ </div>
351
351
  `;
352
352
  }
353
353
  /**
@@ -362,13 +362,13 @@ function generatePaginationButtons(tableId, current, total) {
362
362
  start = Math.max(1, end - maxButtons + 1);
363
363
  }
364
364
  for (let i = start; i <= end; i++) {
365
- buttons.push(`
366
- <button
367
- class="pagination-btn ${i === current ? 'active' : ''}"
368
- onclick="CrudTable.goToPage('${tableId}', ${i})"
369
- >
370
- ${i}
371
- </button>
365
+ buttons.push(`
366
+ <button
367
+ class="pagination-btn ${i === current ? 'active' : ''}"
368
+ onclick="CrudTable.goToPage('${tableId}', ${i})"
369
+ >
370
+ ${i}
371
+ </button>
372
372
  `);
373
373
  }
374
374
  return buttons.join('');
@@ -419,353 +419,353 @@ function getBadgeClass(value) {
419
419
  * 获取 CrudTable 客户端脚本
420
420
  */
421
421
  function getCrudTableScript() {
422
- return `
423
- // CrudTable 全局对象
424
- window.CrudTable = {
425
- // 存储表格配置
426
- configs: {},
427
-
428
- // 初始化表格
429
- init: function(tableId, config) {
430
- this.configs[tableId] = config;
431
- },
432
-
433
- // 显示创建模态框
434
- showCreate: function(tableId) {
435
- const modal = document.getElementById('crud-' + tableId + '-create-modal');
436
- const form = document.getElementById('crud-' + tableId + '-create-form');
437
- if (form) form.reset();
438
- this.hideError(tableId, 'create');
439
- modal.classList.add('active');
440
- },
441
-
442
- // 显示编辑模态框
443
- showEdit: function(tableId, recordId) {
444
- const modal = document.getElementById('crud-' + tableId + '-edit-modal');
445
- const idInput = document.getElementById('crud-' + tableId + '-edit-id');
446
- idInput.value = recordId;
447
-
448
- // 获取当前行数据填充表单
449
- const row = document.querySelector('#crud-' + tableId + '-tbody tr[data-id="' + recordId + '"]');
450
- if (row) {
451
- // 从服务器获取数据并填充表单
452
- this.loadRecordData(tableId, recordId);
453
- }
454
-
455
- this.hideError(tableId, 'edit');
456
- modal.classList.add('active');
457
- },
458
-
459
- // 加载记录数据
460
- loadRecordData: async function(tableId, recordId) {
461
- try {
462
- const res = await fetch('/api/v1/admin/' + tableId + '/' + recordId);
463
- if (res.ok) {
464
- const data = await res.json();
465
- if (data.data) {
466
- this.fillForm(tableId, 'edit', data.data);
467
- }
468
- }
469
- } catch (err) {
470
- console.error('Failed to load record:', err);
471
- }
472
- },
473
-
474
- // 填充表单
475
- fillForm: function(tableId, formType, data) {
476
- Object.keys(data).forEach(function(key) {
477
- const input = document.getElementById('crud-' + tableId + '-' + formType + '-' + key);
478
- if (input) {
479
- if (input.type === 'hidden' && input.closest('.icon-picker')) {
480
- // 图标选择器
481
- input.value = data[key];
482
- const options = input.parentElement.querySelectorAll('.icon-option');
483
- options.forEach(function(opt) {
484
- opt.classList.toggle('selected', opt.dataset.icon === data[key]);
485
- });
486
- } else {
487
- input.value = data[key] || '';
488
- }
489
- }
490
- });
491
- },
492
-
493
- // 关闭模态框
494
- closeModal: function(modalId) {
495
- document.getElementById(modalId).classList.remove('active');
496
- },
497
-
498
- // 创建记录
499
- create: async function(tableId, event) {
500
- event.preventDefault();
501
- const form = event.target;
502
- const formData = new FormData(form);
503
- const data = Object.fromEntries(formData.entries());
504
-
505
- try {
506
- const res = await fetch('/api/v1/admin/' + tableId, {
507
- method: 'POST',
508
- headers: { 'Content-Type': 'application/json' },
509
- body: JSON.stringify(data)
510
- });
511
-
512
- if (res.ok) {
513
- this.closeModal('crud-' + tableId + '-create-modal');
514
- this.refresh(tableId);
515
- } else {
516
- const err = await res.json();
517
- this.showError(tableId, 'create', err.message || '创建失败');
518
- }
519
- } catch (err) {
520
- this.showError(tableId, 'create', '请求失败: ' + err.message);
521
- }
522
- },
523
-
524
- // 更新记录
525
- update: async function(tableId, event) {
526
- event.preventDefault();
527
- const form = event.target;
528
- const formData = new FormData(form);
529
- const data = Object.fromEntries(formData.entries());
530
- const id = data.id;
531
- delete data.id;
532
-
533
- try {
534
- const res = await fetch('/api/v1/admin/' + tableId + '/' + id, {
535
- method: 'PUT',
536
- headers: { 'Content-Type': 'application/json' },
537
- body: JSON.stringify(data)
538
- });
539
-
540
- if (res.ok) {
541
- this.closeModal('crud-' + tableId + '-edit-modal');
542
- this.refresh(tableId);
543
- } else {
544
- const err = await res.json();
545
- this.showError(tableId, 'edit', err.message || '更新失败');
546
- }
547
- } catch (err) {
548
- this.showError(tableId, 'edit', '请求失败: ' + err.message);
549
- }
550
- },
551
-
552
- // 确认删除
553
- confirmDelete: async function(tableId, recordId) {
554
- const config = this.configs[tableId] || {};
555
- const message = config.deleteConfirmMessage || '确定要删除这条记录吗?';
556
-
557
- if (confirm(message)) {
558
- try {
559
- const res = await fetch('/api/v1/admin/' + tableId + '/' + recordId, {
560
- method: 'DELETE'
561
- });
562
-
563
- if (res.ok) {
564
- this.refresh(tableId);
565
- } else {
566
- const err = await res.json();
567
- alert('删除失败: ' + (err.message || '未知错误'));
568
- }
569
- } catch (err) {
570
- alert('请求失败: ' + err.message);
571
- }
572
- }
573
- },
574
-
575
- // 搜索
576
- search: function(tableId, event) {
577
- event.preventDefault();
578
- const input = document.getElementById('crud-' + tableId + '-search');
579
- const query = input.value;
580
- const params = new URLSearchParams(window.location.search);
581
- params.set('q', query);
582
- params.set('page', '1');
583
- window.location.search = params.toString();
584
- },
585
-
586
- // 刷新
587
- refresh: function(tableId) {
588
- window.location.reload();
589
- },
590
-
591
- // 跳转页面
592
- goToPage: function(tableId, page) {
593
- const params = new URLSearchParams(window.location.search);
594
- params.set('page', page);
595
- window.location.search = params.toString();
596
- },
597
-
598
- // 选择图标
599
- selectIcon: function(inputId, iconValue, button) {
600
- const input = document.getElementById(inputId);
601
- input.value = iconValue;
602
-
603
- const options = button.parentElement.querySelectorAll('.icon-option');
604
- options.forEach(function(opt) {
605
- opt.classList.remove('selected');
606
- });
607
- button.classList.add('selected');
608
- },
609
-
610
- // 显示错误
611
- showError: function(tableId, formType, message) {
612
- const errorDiv = document.getElementById('crud-' + tableId + '-' + formType + '-error');
613
- if (errorDiv) {
614
- errorDiv.textContent = message;
615
- errorDiv.style.display = 'flex';
616
- }
617
- },
618
-
619
- // 隐藏错误
620
- hideError: function(tableId, formType) {
621
- const errorDiv = document.getElementById('crud-' + tableId + '-' + formType + '-error');
622
- if (errorDiv) {
623
- errorDiv.style.display = 'none';
624
- }
625
- }
626
- };
422
+ return `
423
+ // CrudTable 全局对象
424
+ window.CrudTable = {
425
+ // 存储表格配置
426
+ configs: {},
427
+
428
+ // 初始化表格
429
+ init: function(tableId, config) {
430
+ this.configs[tableId] = config;
431
+ },
432
+
433
+ // 显示创建模态框
434
+ showCreate: function(tableId) {
435
+ const modal = document.getElementById('crud-' + tableId + '-create-modal');
436
+ const form = document.getElementById('crud-' + tableId + '-create-form');
437
+ if (form) form.reset();
438
+ this.hideError(tableId, 'create');
439
+ modal.classList.add('active');
440
+ },
441
+
442
+ // 显示编辑模态框
443
+ showEdit: function(tableId, recordId) {
444
+ const modal = document.getElementById('crud-' + tableId + '-edit-modal');
445
+ const idInput = document.getElementById('crud-' + tableId + '-edit-id');
446
+ idInput.value = recordId;
447
+
448
+ // 获取当前行数据填充表单
449
+ const row = document.querySelector('#crud-' + tableId + '-tbody tr[data-id="' + recordId + '"]');
450
+ if (row) {
451
+ // 从服务器获取数据并填充表单
452
+ this.loadRecordData(tableId, recordId);
453
+ }
454
+
455
+ this.hideError(tableId, 'edit');
456
+ modal.classList.add('active');
457
+ },
458
+
459
+ // 加载记录数据
460
+ loadRecordData: async function(tableId, recordId) {
461
+ try {
462
+ const res = await fetch('/api/v1/admin/' + tableId + '/' + recordId);
463
+ if (res.ok) {
464
+ const data = await res.json();
465
+ if (data.data) {
466
+ this.fillForm(tableId, 'edit', data.data);
467
+ }
468
+ }
469
+ } catch (err) {
470
+ console.error('Failed to load record:', err);
471
+ }
472
+ },
473
+
474
+ // 填充表单
475
+ fillForm: function(tableId, formType, data) {
476
+ Object.keys(data).forEach(function(key) {
477
+ const input = document.getElementById('crud-' + tableId + '-' + formType + '-' + key);
478
+ if (input) {
479
+ if (input.type === 'hidden' && input.closest('.icon-picker')) {
480
+ // 图标选择器
481
+ input.value = data[key];
482
+ const options = input.parentElement.querySelectorAll('.icon-option');
483
+ options.forEach(function(opt) {
484
+ opt.classList.toggle('selected', opt.dataset.icon === data[key]);
485
+ });
486
+ } else {
487
+ input.value = data[key] || '';
488
+ }
489
+ }
490
+ });
491
+ },
492
+
493
+ // 关闭模态框
494
+ closeModal: function(modalId) {
495
+ document.getElementById(modalId).classList.remove('active');
496
+ },
497
+
498
+ // 创建记录
499
+ create: async function(tableId, event) {
500
+ event.preventDefault();
501
+ const form = event.target;
502
+ const formData = new FormData(form);
503
+ const data = Object.fromEntries(formData.entries());
504
+
505
+ try {
506
+ const res = await fetch('/api/v1/admin/' + tableId, {
507
+ method: 'POST',
508
+ headers: { 'Content-Type': 'application/json' },
509
+ body: JSON.stringify(data)
510
+ });
511
+
512
+ if (res.ok) {
513
+ this.closeModal('crud-' + tableId + '-create-modal');
514
+ this.refresh(tableId);
515
+ } else {
516
+ const err = await res.json();
517
+ this.showError(tableId, 'create', err.message || '创建失败');
518
+ }
519
+ } catch (err) {
520
+ this.showError(tableId, 'create', '请求失败: ' + err.message);
521
+ }
522
+ },
523
+
524
+ // 更新记录
525
+ update: async function(tableId, event) {
526
+ event.preventDefault();
527
+ const form = event.target;
528
+ const formData = new FormData(form);
529
+ const data = Object.fromEntries(formData.entries());
530
+ const id = data.id;
531
+ delete data.id;
532
+
533
+ try {
534
+ const res = await fetch('/api/v1/admin/' + tableId + '/' + id, {
535
+ method: 'PUT',
536
+ headers: { 'Content-Type': 'application/json' },
537
+ body: JSON.stringify(data)
538
+ });
539
+
540
+ if (res.ok) {
541
+ this.closeModal('crud-' + tableId + '-edit-modal');
542
+ this.refresh(tableId);
543
+ } else {
544
+ const err = await res.json();
545
+ this.showError(tableId, 'edit', err.message || '更新失败');
546
+ }
547
+ } catch (err) {
548
+ this.showError(tableId, 'edit', '请求失败: ' + err.message);
549
+ }
550
+ },
551
+
552
+ // 确认删除
553
+ confirmDelete: async function(tableId, recordId) {
554
+ const config = this.configs[tableId] || {};
555
+ const message = config.deleteConfirmMessage || '确定要删除这条记录吗?';
556
+
557
+ if (confirm(message)) {
558
+ try {
559
+ const res = await fetch('/api/v1/admin/' + tableId + '/' + recordId, {
560
+ method: 'DELETE'
561
+ });
562
+
563
+ if (res.ok) {
564
+ this.refresh(tableId);
565
+ } else {
566
+ const err = await res.json();
567
+ alert('删除失败: ' + (err.message || '未知错误'));
568
+ }
569
+ } catch (err) {
570
+ alert('请求失败: ' + err.message);
571
+ }
572
+ }
573
+ },
574
+
575
+ // 搜索
576
+ search: function(tableId, event) {
577
+ event.preventDefault();
578
+ const input = document.getElementById('crud-' + tableId + '-search');
579
+ const query = input.value;
580
+ const params = new URLSearchParams(window.location.search);
581
+ params.set('q', query);
582
+ params.set('page', '1');
583
+ window.location.search = params.toString();
584
+ },
585
+
586
+ // 刷新
587
+ refresh: function(tableId) {
588
+ window.location.reload();
589
+ },
590
+
591
+ // 跳转页面
592
+ goToPage: function(tableId, page) {
593
+ const params = new URLSearchParams(window.location.search);
594
+ params.set('page', page);
595
+ window.location.search = params.toString();
596
+ },
597
+
598
+ // 选择图标
599
+ selectIcon: function(inputId, iconValue, button) {
600
+ const input = document.getElementById(inputId);
601
+ input.value = iconValue;
602
+
603
+ const options = button.parentElement.querySelectorAll('.icon-option');
604
+ options.forEach(function(opt) {
605
+ opt.classList.remove('selected');
606
+ });
607
+ button.classList.add('selected');
608
+ },
609
+
610
+ // 显示错误
611
+ showError: function(tableId, formType, message) {
612
+ const errorDiv = document.getElementById('crud-' + tableId + '-' + formType + '-error');
613
+ if (errorDiv) {
614
+ errorDiv.textContent = message;
615
+ errorDiv.style.display = 'flex';
616
+ }
617
+ },
618
+
619
+ // 隐藏错误
620
+ hideError: function(tableId, formType) {
621
+ const errorDiv = document.getElementById('crud-' + tableId + '-' + formType + '-error');
622
+ if (errorDiv) {
623
+ errorDiv.style.display = 'none';
624
+ }
625
+ }
626
+ };
627
627
  `;
628
628
  }
629
629
  /**
630
630
  * 获取 CrudTable 专用样式
631
631
  */
632
632
  function getCrudTableStyles() {
633
- return `
634
- /* CrudTable 专用样式 */
635
- .crud-table {
636
- margin-bottom: 24px;
637
- }
638
-
639
- .crud-header {
640
- display: flex;
641
- justify-content: space-between;
642
- align-items: center;
643
- margin-bottom: 20px;
644
- }
645
-
646
- .crud-title {
647
- display: flex;
648
- align-items: center;
649
- gap: 12px;
650
- }
651
-
652
- .crud-title svg {
653
- width: 28px;
654
- height: 28px;
655
- color: var(--primary);
656
- }
657
-
658
- .crud-title h1 {
659
- font-size: 24px;
660
- font-weight: 600;
661
- color: var(--text-primary);
662
- margin: 0;
663
- }
664
-
665
- .crud-count {
666
- font-size: 14px;
667
- color: var(--text-muted);
668
- background: var(--bg-dark);
669
- padding: 4px 12px;
670
- border-radius: 20px;
671
- }
672
-
673
- .crud-actions {
674
- display: flex;
675
- gap: 12px;
676
- }
677
-
678
- .crud-search {
679
- margin-bottom: 20px;
680
- }
681
-
682
- .crud-search form {
683
- display: flex;
684
- gap: 12px;
685
- max-width: 500px;
686
- }
687
-
688
- .crud-search .search-box {
689
- flex: 1;
690
- }
691
-
692
- .crud-pagination {
693
- display: flex;
694
- justify-content: space-between;
695
- align-items: center;
696
- padding: 16px 20px;
697
- border-top: 1px solid var(--border-color);
698
- }
699
-
700
- .actions-column {
701
- width: 120px;
702
- text-align: right;
703
- }
704
-
705
- .actions-cell {
706
- text-align: right;
707
- }
708
-
709
- /* 按钮颜色扩展 */
710
- .btn-success {
711
- background: var(--success);
712
- color: white;
713
- }
714
-
715
- .btn-success:hover {
716
- background: #16a34a;
717
- }
718
-
719
- /* 图标选择器样式 */
720
- .icon-picker {
721
- display: flex;
722
- flex-direction: column;
723
- gap: 8px;
724
- }
725
-
726
- .icon-options {
727
- display: flex;
728
- flex-wrap: wrap;
729
- gap: 8px;
730
- }
731
-
732
- .icon-option {
733
- width: 40px;
734
- height: 40px;
735
- font-size: 20px;
736
- display: flex;
737
- align-items: center;
738
- justify-content: center;
739
- border: 2px solid var(--border-color);
740
- border-radius: var(--border-radius);
741
- background: var(--bg-dark);
742
- cursor: pointer;
743
- transition: var(--transition);
744
- }
745
-
746
- .icon-option:hover {
747
- border-color: var(--text-muted);
748
- }
749
-
750
- .icon-option.selected {
751
- border-color: var(--primary);
752
- background: rgba(99, 102, 241, 0.2);
753
- }
754
-
755
- .icon-display {
756
- font-size: 20px;
757
- }
758
-
759
- /* 必填标记 */
760
- .required {
761
- color: var(--danger);
762
- margin-left: 4px;
763
- }
764
-
765
- /* 保存图标 */
766
- .btn svg {
767
- flex-shrink: 0;
768
- }
633
+ return `
634
+ /* CrudTable 专用样式 */
635
+ .crud-table {
636
+ margin-bottom: 24px;
637
+ }
638
+
639
+ .crud-header {
640
+ display: flex;
641
+ justify-content: space-between;
642
+ align-items: center;
643
+ margin-bottom: 20px;
644
+ }
645
+
646
+ .crud-title {
647
+ display: flex;
648
+ align-items: center;
649
+ gap: 12px;
650
+ }
651
+
652
+ .crud-title svg {
653
+ width: 28px;
654
+ height: 28px;
655
+ color: var(--primary);
656
+ }
657
+
658
+ .crud-title h1 {
659
+ font-size: 24px;
660
+ font-weight: 600;
661
+ color: var(--text-primary);
662
+ margin: 0;
663
+ }
664
+
665
+ .crud-count {
666
+ font-size: 14px;
667
+ color: var(--text-muted);
668
+ background: var(--bg-dark);
669
+ padding: 4px 12px;
670
+ border-radius: 20px;
671
+ }
672
+
673
+ .crud-actions {
674
+ display: flex;
675
+ gap: 12px;
676
+ }
677
+
678
+ .crud-search {
679
+ margin-bottom: 20px;
680
+ }
681
+
682
+ .crud-search form {
683
+ display: flex;
684
+ gap: 12px;
685
+ max-width: 500px;
686
+ }
687
+
688
+ .crud-search .search-box {
689
+ flex: 1;
690
+ }
691
+
692
+ .crud-pagination {
693
+ display: flex;
694
+ justify-content: space-between;
695
+ align-items: center;
696
+ padding: 16px 20px;
697
+ border-top: 1px solid var(--border-color);
698
+ }
699
+
700
+ .actions-column {
701
+ width: 120px;
702
+ text-align: right;
703
+ }
704
+
705
+ .actions-cell {
706
+ text-align: right;
707
+ }
708
+
709
+ /* 按钮颜色扩展 */
710
+ .btn-success {
711
+ background: var(--success);
712
+ color: white;
713
+ }
714
+
715
+ .btn-success:hover {
716
+ background: #16a34a;
717
+ }
718
+
719
+ /* 图标选择器样式 */
720
+ .icon-picker {
721
+ display: flex;
722
+ flex-direction: column;
723
+ gap: 8px;
724
+ }
725
+
726
+ .icon-options {
727
+ display: flex;
728
+ flex-wrap: wrap;
729
+ gap: 8px;
730
+ }
731
+
732
+ .icon-option {
733
+ width: 40px;
734
+ height: 40px;
735
+ font-size: 20px;
736
+ display: flex;
737
+ align-items: center;
738
+ justify-content: center;
739
+ border: 2px solid var(--border-color);
740
+ border-radius: var(--border-radius);
741
+ background: var(--bg-dark);
742
+ cursor: pointer;
743
+ transition: var(--transition);
744
+ }
745
+
746
+ .icon-option:hover {
747
+ border-color: var(--text-muted);
748
+ }
749
+
750
+ .icon-option.selected {
751
+ border-color: var(--primary);
752
+ background: rgba(99, 102, 241, 0.2);
753
+ }
754
+
755
+ .icon-display {
756
+ font-size: 20px;
757
+ }
758
+
759
+ /* 必填标记 */
760
+ .required {
761
+ color: var(--danger);
762
+ margin-left: 4px;
763
+ }
764
+
765
+ /* 保存图标 */
766
+ .btn svg {
767
+ flex-shrink: 0;
768
+ }
769
769
  `;
770
770
  }
771
771
  //# sourceMappingURL=crud-table.js.map