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.
- package/LICENSE +137 -137
- package/{aifastdb.win32-x64-msvc.node → aifastdb.darwin-arm64.node} +0 -0
- package/dist/cli.js +30 -30
- package/dist/llm-gateway.d.ts +73 -6
- package/dist/llm-gateway.d.ts.map +1 -1
- package/dist/llm-gateway.js.map +1 -1
- package/dist/security/server/admin/crud-table.js +603 -603
- package/dist/security/server/admin/icons.js +64 -64
- package/dist/security/server/admin/styles.js +942 -942
- package/dist/security/server/admin/templates.js +866 -866
- package/dist/types.d.ts +19 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +42 -4
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/aidb.win32-x64-msvc.node +0 -0
- package/dist/dev-plan-store.d.ts +0 -656
- package/dist/dev-plan-store.d.ts.map +0 -1
- package/dist/dev-plan-store.js +0 -1679
- package/dist/dev-plan-store.js.map +0 -1
- package/dist/migrate-federation-plan.d.ts +0 -16
- package/dist/migrate-federation-plan.d.ts.map +0 -1
- package/dist/migrate-federation-plan.js +0 -420
- package/dist/migrate-federation-plan.js.map +0 -1
- package/dist/social-graph.d.ts +0 -532
- package/dist/social-graph.d.ts.map +0 -1
- package/dist/social-graph.js +0 -997
- package/dist/social-graph.js.map +0 -1
- package/vibebase.node +0 -0
- package/vibebase.win32-x64-msvc.node +0 -0
|
@@ -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
|