flameresttable 1.0.119 → 1.0.122
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/package.json +17 -17
- package/src/App.vue +16 -17
- package/src/Table/Columns.ts +66 -44
- package/src/Table/FlameTable.ts +90 -17
- package/src/Table/Paginator.vue +37 -19
- package/src/Table/Table.vue +23 -247
- package/src/Table/TableFilters.vue +89 -19
- package/src/Table/TableOpts.ts +5 -0
- package/src/Table/TablePopup.vue +324 -0
- package/src/Table/TableSelectorItem.ts +22 -2
- package/src/Table/TableTasksPanel.vue +1 -1
- package/src/components/default/Modal.vue +37 -32
- package/src/components/editor/EditorPopup.vue +1 -1
- package/src/components/editor/LinkDialog.vue +1 -1
- package/src/components/editor/SelectImage.vue +1 -1
- package/src/main.ts +4 -6
- package/src/pages/Home.vue +5 -6
- package/src/pages/SubRow.vue +28 -24
package/src/Table/Table.vue
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<div v-for="column in Table.columns" v-show="column.Table.isShow" :key="'cheader_' + column.name"
|
|
36
36
|
:class="' defaultHeader ' + column.Table.classesHeader"
|
|
37
37
|
class=" table-cell align-middle border-r border-r-slate-100 px-2"
|
|
38
|
-
:style="(column.Table.width === null ? '' :
|
|
38
|
+
:style="(column.Table.width === null ? '' : `width: ${column.Table.width}px; min-width: ${column.Table.width}px`)">
|
|
39
39
|
<span>{{ column.Table.titleCustom !== null ? (column as any).Table.titleCustom(column) : column.Table.title
|
|
40
40
|
!==
|
|
41
41
|
'' ?
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
:class="' defaultCell ' + column.Table.classes"
|
|
77
77
|
class="table-cell align-middle border-r border-r-slate-100 px-2 last:border-r-0 last:pr-0"
|
|
78
78
|
@click="columnClick(row, column)"
|
|
79
|
-
:style="(column.Table.width === null ? '' :
|
|
79
|
+
:style="(column.Table.width === null ? '' : `width: ${column.Table.width}px; min-width: ${column.Table.width}px`)">
|
|
80
80
|
|
|
81
81
|
<span v-if="!column.Table.isRawValue">{{
|
|
82
82
|
column.Table.value(row, column) }}</span>
|
|
@@ -119,137 +119,18 @@
|
|
|
119
119
|
<Paginator class="mt-3" :table="Table" v-if="opts.Pagination.type === 'pages'" />
|
|
120
120
|
|
|
121
121
|
<!-- МОДАЛКА ДОБАВЛЕНИЯ-->
|
|
122
|
-
<
|
|
123
|
-
<template #default>
|
|
124
|
-
<div class="bg-white rounded-xl" :class="'w-full desktop:w-[777px]'">
|
|
125
|
-
|
|
126
|
-
<!-- СЛОТ ЗАГОЛОВКА ПОПАПА -->
|
|
127
|
-
<slot name="header" />
|
|
128
|
-
|
|
129
|
-
<!-- КОЛОНКИ ТАБЛИЦЫ-->
|
|
130
|
-
<label v-for="column in ColumnNames" v-show="Table.columns[column].Popup.isShow" :key="'c' + column"
|
|
131
|
-
class="flex items-stretch justify-center w-full my-1 ml-2">
|
|
132
|
-
|
|
133
|
-
<!-- Заголовок -->
|
|
134
|
-
<div class="text-left px-2 py-1 bg-slate-100" style="width: 110px">
|
|
135
|
-
<div>{{ Table.columns[column].Popup.title !== '' ? Table.columns[column].Popup.title === '' :
|
|
136
|
-
Table.columns[column].title !== '' ? Table.columns[column].title : Table.columns[column].name }}</div>
|
|
137
|
-
<div class="text-xs text-slate-400">{{ Table.columns[column].Popup.desc }}</div>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
<div v-if="Table.columns[column].Popup.popupType === 'string'"
|
|
141
|
-
class="flex-1 mr-5 w-full flex flex-col self-stretch">
|
|
142
|
-
<input v-model="Table.columns[column].Popup.model"
|
|
143
|
-
class="h-full self-stretch py-1 px-2 w-full outline-none border border-slate-100" type="text"
|
|
144
|
-
:disabled="!Table.columns[column].Popup.isEnabled"
|
|
145
|
-
:placeholder="Table.columns[column].Popup.placeholder !== '' ? Table.columns[column].Popup.placeholder : Table.columns[column].title !== '' ? Table.columns[column].title : Table.columns[column].name">
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
<div v-if="Table.columns[column].Popup.popupType === 'date'"
|
|
149
|
-
class="flex-1 mr-5 w-full flex flex-col self-stretch">
|
|
150
|
-
<Datepicker v-model="Table.columns[column].Popup.model" :auto-apply="true" />
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
<div v-if="Table.columns[column].Popup.popupType === 'selector'"
|
|
154
|
-
class="flex-1 mr-5 w-full flex flex-col self-stretch"><select v-model="Table.columns[column].Popup.model"
|
|
155
|
-
class="h-full self-stretch py-1 px-2 w-full outline-none border border-slate-100">
|
|
156
|
-
<option v-for="(selectorVal, selectorKey) in Table.columns[column].Popup.Selector.values"
|
|
157
|
-
:key="'sel_' + selectorKey"
|
|
158
|
-
:value="getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[1]">{{
|
|
159
|
-
getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[0] }}</option>
|
|
160
|
-
</select>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<!-- КАРТИНКА -->
|
|
164
|
-
<div v-if="Table.columns[column].Popup.popupType === 'image'"
|
|
165
|
-
class="flex-1 border border-slate-100 mr-5 w-full flex self-stretch">
|
|
166
|
-
<div class="flex-1">
|
|
167
|
-
<img class="max-w-[200px]" :src="fileGetData(Table.columns[column].Popup.model, true)">
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
<!-- Загрузчик-->
|
|
171
|
-
<label :for="'cfile_' + column">
|
|
172
|
-
|
|
173
|
-
<svg class="my-auto cursor-pointer" width="24" height="24" viewbox="0 0 24 24" fill="none"
|
|
174
|
-
xmlns="http://www.w3.org/2000/svg">
|
|
175
|
-
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
176
|
-
d="M11.4697 2.46967C11.7626 2.17678 12.2374 2.17678 12.5303 2.46967L17.0303 6.96967C17.3232 7.26256 17.3232 7.73744 17.0303 8.03033C16.7374 8.32322 16.2626 8.32322 15.9697 8.03033L12.75 4.81066L12.75 16.5C12.75 16.9142 12.4142 17.25 12 17.25C11.5858 17.25 11.25 16.9142 11.25 16.5L11.25 4.81066L8.03033 8.03033C7.73744 8.32322 7.26256 8.32322 6.96967 8.03033C6.67678 7.73744 6.67678 7.26256 6.96967 6.96967L11.4697 2.46967ZM3 15.75C3.41421 15.75 3.75 16.0858 3.75 16.5V18.75C3.75 19.5784 4.42157 20.25 5.25 20.25H18.75C19.5784 20.25 20.25 19.5784 20.25 18.75V16.5C20.25 16.0858 20.5858 15.75 21 15.75C21.4142 15.75 21.75 16.0858 21.75 16.5V18.75C21.75 20.4069 20.4069 21.75 18.75 21.75H5.25C3.59315 21.75 2.25 20.4069 2.25 18.75V16.5C2.25 16.0858 2.58579 15.75 3 15.75Z"
|
|
177
|
-
fill="#0F172A" />
|
|
178
|
-
</svg>
|
|
179
|
-
|
|
180
|
-
<input class="hidden" :name="'cfile_' + column" type="file" @change="fileUpdated(column, $event)">
|
|
181
|
-
|
|
182
|
-
</label>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
<!-- ФАЙЛЫ -->
|
|
186
|
-
<div v-if="Table.columns[column].Popup.popupType === 'file'"
|
|
187
|
-
class="flex-1 border border-slate-100 mr-5 w-full flex self-stretch">
|
|
188
|
-
|
|
189
|
-
<div class="flex-1">
|
|
190
|
-
{{ getFileName(Table.columns[column].Popup.model) }}
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
<!-- Загрузчик-->
|
|
194
|
-
<label :for="'cfile_' + column">
|
|
195
|
-
|
|
196
|
-
<svg class="my-auto cursor-pointer" width="24" height="24" viewbox="0 0 24 24" fill="none"
|
|
197
|
-
xmlns="http://www.w3.org/2000/svg">
|
|
198
|
-
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
199
|
-
d="M11.4697 2.46967C11.7626 2.17678 12.2374 2.17678 12.5303 2.46967L17.0303 6.96967C17.3232 7.26256 17.3232 7.73744 17.0303 8.03033C16.7374 8.32322 16.2626 8.32322 15.9697 8.03033L12.75 4.81066L12.75 16.5C12.75 16.9142 12.4142 17.25 12 17.25C11.5858 17.25 11.25 16.9142 11.25 16.5L11.25 4.81066L8.03033 8.03033C7.73744 8.32322 7.26256 8.32322 6.96967 8.03033C6.67678 7.73744 6.67678 7.26256 6.96967 6.96967L11.4697 2.46967ZM3 15.75C3.41421 15.75 3.75 16.0858 3.75 16.5V18.75C3.75 19.5784 4.42157 20.25 5.25 20.25H18.75C19.5784 20.25 20.25 19.5784 20.25 18.75V16.5C20.25 16.0858 20.5858 15.75 21 15.75C21.4142 15.75 21.75 16.0858 21.75 16.5V18.75C21.75 20.4069 20.4069 21.75 18.75 21.75H5.25C3.59315 21.75 2.25 20.4069 2.25 18.75V16.5C2.25 16.0858 2.58579 15.75 3 15.75Z"
|
|
200
|
-
fill="#0F172A" />
|
|
201
|
-
</svg>
|
|
202
|
-
|
|
203
|
-
<input class="hidden" :name="'cfile_' + column" type="file" @change="fileUpdated(column, $event)">
|
|
204
|
-
|
|
205
|
-
</label>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div class="flex-1 mr-5 w-full flex flex-col self-stretch"
|
|
209
|
-
v-if="Table.columns[column].Popup.popupType === 'text'">
|
|
210
|
-
<EditorPopup :value="Table.columns[column].Popup.model" @save="PopupTextSaved" :column="column" :title="Table.columns[column].Popup.title ?? column">
|
|
211
|
-
|
|
212
|
-
</EditorPopup>
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
<!-- ОШИБКИ СОХРАНЕНИЯ -->
|
|
216
|
-
<div class="mx-2 my-1 text-red-600" v-if="(typeof popupErrors[column]!== 'undefined')">{{ popupErrors[column]
|
|
217
|
-
}}</div>
|
|
218
|
-
|
|
219
|
-
</label>
|
|
220
|
-
|
|
221
|
-
<div class="text-red-600 mx-2 my-1" v-if="(typeof popupErrors['globalErrorStack']!== 'undefined')">{{
|
|
222
|
-
popupErrors['globalErrorStack'] }}</div>
|
|
223
|
-
|
|
224
|
-
<!-- КНОПКИ СОХРАНЕНИЯ-->
|
|
225
|
-
<div class="flex w-full mt-3 mb-2 justify-between items-center">
|
|
226
|
-
<div v-show="$props.opts?.Add.can"
|
|
227
|
-
class="cursor-pointer ml-2 text-lg py-2 text-white block bg-gray-400 font-medium rounded px-4 text-center hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-900"
|
|
228
|
-
@click="Table.OpenedRow = null; FlameTableModal?.close()">
|
|
229
|
-
Отмена
|
|
230
|
-
</div>
|
|
231
|
-
<div
|
|
232
|
-
class="cursor-pointer ml-2 text-lg py-2 mr-3 text-white block bg-blue-600 font-medium rounded px-4 text-center hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-900"
|
|
233
|
-
@click=" SaveTable()">
|
|
234
|
-
{{ Table.mode === 'add' ? 'Добавить' : 'Сохранить' }}
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
</template>
|
|
239
|
-
</ModalVue>
|
|
122
|
+
<TablePopup :table="Table" ref="FlameTablePopup"></TablePopup>
|
|
240
123
|
</template>
|
|
241
124
|
|
|
242
125
|
<script lang="ts">
|
|
243
|
-
import { onMounted, reactive, ref
|
|
126
|
+
import { onMounted, reactive, ref } from 'vue'; import type { Ref } from 'vue'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST, { Rows } from "flamerest"
|
|
244
127
|
|
|
245
128
|
// Иконки
|
|
246
|
-
import { computed } from '@vue/reactivity';
|
|
247
129
|
import { Column, IColumn } from './Columns';
|
|
248
130
|
import { defineComponent } from 'vue';
|
|
249
131
|
|
|
250
132
|
import TableOpts from './TableOpts';
|
|
251
133
|
import FlameTable from './FlameTable';
|
|
252
|
-
import ModalVue from './../components/default/Modal.vue';
|
|
253
134
|
import Paginator from './Paginator.vue';
|
|
254
135
|
import TableFilters from './TableFilters.vue';
|
|
255
136
|
type Class<T> = new (...args: any[]) => T;
|
|
@@ -257,12 +138,11 @@ type Class<T> = new (...args: any[]) => T;
|
|
|
257
138
|
import Datepicker from '@vuepic/vue-datepicker';
|
|
258
139
|
import '@vuepic/vue-datepicker/dist/main.css';
|
|
259
140
|
import { isNumber } from 'lodash';
|
|
260
|
-
import 'vue-select/dist/vue-select.css';
|
|
261
141
|
import TableTasksPanel from './TableTasksPanel.vue';
|
|
262
|
-
import
|
|
142
|
+
import TablePopup, { type TablePopupInstance } from './TablePopup.vue';
|
|
263
143
|
|
|
264
144
|
export default defineComponent({
|
|
265
|
-
components: {
|
|
145
|
+
components: { Paginator, Datepicker, TableFilters, TableTasksPanel, TablePopup },
|
|
266
146
|
props: {
|
|
267
147
|
rows: {
|
|
268
148
|
default: [] as Array<any>,
|
|
@@ -282,81 +162,33 @@ export default defineComponent({
|
|
|
282
162
|
|
|
283
163
|
const Table = (new FlameTable(props.model as any, props.opts)) as FlameTable<any>;
|
|
284
164
|
|
|
285
|
-
const
|
|
165
|
+
const FlameTablePopup = ref<TablePopupInstance>();
|
|
286
166
|
|
|
287
|
-
const ColumnNames = Object.keys(Table.
|
|
288
|
-
|
|
289
|
-
// Ошибки сохранения
|
|
290
|
-
let popupErrors: {[key:string]:string} = reactive({});
|
|
291
|
-
|
|
292
|
-
// Добавляем событие скролла
|
|
167
|
+
const ColumnNames = Object.keys(Table.columns);
|
|
293
168
|
|
|
294
169
|
/**
|
|
295
170
|
* Добавление новой записи
|
|
296
171
|
*/
|
|
297
172
|
const add = () => {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// Установим все поля в пустые значения
|
|
301
|
-
ColumnNames.forEach(key => {
|
|
302
|
-
Table.columns[key].Popup.model = "";
|
|
303
|
-
Table.columns[key].Popup.fileModel = null;
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
FlameTableModal.value?.show();
|
|
173
|
+
FlameTablePopup.value?.showAdd();
|
|
307
174
|
}
|
|
308
175
|
|
|
309
176
|
/**
|
|
310
177
|
* Редактирование записи
|
|
311
178
|
*/
|
|
312
179
|
const edit = async (row: any) => {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
Table.columns[key].Popup.model = row[key];
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
Table.OpenedRow = row;
|
|
327
|
-
|
|
328
|
-
FlameTableModal.value?.show();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const SaveTable = async () => {
|
|
332
|
-
|
|
333
|
-
try {
|
|
334
|
-
if (Table.mode === 'add') {
|
|
335
|
-
await Table.add()
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
await Table.save()
|
|
339
|
-
}
|
|
340
|
-
popupErrors={};
|
|
341
|
-
}
|
|
342
|
-
catch (ex: any) {
|
|
343
|
-
|
|
344
|
-
for (const keyx in popupErrors)
|
|
345
|
-
delete popupErrors[keyx];
|
|
346
|
-
|
|
347
|
-
if(typeof ex.stack !== 'undefined' && typeof ex.message !== 'undefined') {
|
|
348
|
-
popupErrors.globalErrorStack = ex.message;
|
|
349
|
-
}
|
|
350
|
-
else
|
|
351
|
-
Object.assign(popupErrors, ex);
|
|
352
|
-
|
|
353
|
-
return;
|
|
180
|
+
// TODO OPTIMIZATION NOTE: Логирование для отладки открытия попапа редактирования
|
|
181
|
+
console.log('[FlameTable Debug] edit method called', {
|
|
182
|
+
popupInstance: FlameTablePopup.value,
|
|
183
|
+
rowData: row,
|
|
184
|
+
hasShowEdit: typeof FlameTablePopup.value?.showEdit === 'function'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!FlameTablePopup.value) {
|
|
188
|
+
console.error('[FlameTable Debug] FlameTablePopup ref is NULL. Убедитесь, что TablePopup примонтирован и ref="FlameTablePopup" указан верно.');
|
|
354
189
|
}
|
|
355
190
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Table.OpenedRow = null;
|
|
359
|
-
|
|
191
|
+
FlameTablePopup.value?.showEdit(row);
|
|
360
192
|
}
|
|
361
193
|
|
|
362
194
|
const deleteRow = async (row: any) => {
|
|
@@ -368,7 +200,7 @@ export default defineComponent({
|
|
|
368
200
|
const fileUpdated = async (column: any, ev: any) => {
|
|
369
201
|
// если файлы не выбраны
|
|
370
202
|
if (ev.target.files.length === 0) return;
|
|
371
|
-
|
|
203
|
+
|
|
372
204
|
Table.columns[column].Popup.model = ev;
|
|
373
205
|
const prepared = (await REST.prepare({ myParam: ev })).myParam;
|
|
374
206
|
Table.columns[column].Popup.model = prepared
|
|
@@ -386,17 +218,9 @@ export default defineComponent({
|
|
|
386
218
|
})
|
|
387
219
|
}*/
|
|
388
220
|
|
|
389
|
-
const getSelector = (arr: any, selectorKey: any, selectorVal: any) => {
|
|
390
|
-
if (Array.isArray(arr)) return [selectorVal, selectorVal];
|
|
391
|
-
// TODO: тут загрузка объекта должна быть
|
|
392
|
-
// сейчас объект перерабатывается где-то в map и теряет ключи и значения
|
|
393
|
-
if (typeof arr === 'object' && arr !== null) return [selectorKey, selectorVal]
|
|
394
|
-
return [selectorKey, selectorVal]
|
|
395
|
-
}
|
|
396
|
-
|
|
397
221
|
// Автозагрузка первичной версии
|
|
398
222
|
if (props.opts.autoload === true) {
|
|
399
|
-
/**
|
|
223
|
+
/**
|
|
400
224
|
const tModel = (props.model as any)
|
|
401
225
|
tModel.all().then((r: any) => {
|
|
402
226
|
Table.load(r);
|
|
@@ -425,33 +249,19 @@ export default defineComponent({
|
|
|
425
249
|
return Table.RowsParams[row[primaryKey]].collapsed
|
|
426
250
|
}
|
|
427
251
|
|
|
428
|
-
const PopupTextSaved = (txt: string, column: string)=> {
|
|
429
|
-
Table.columns[column].Popup.model = txt;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
252
|
const MainTableElement = ref();
|
|
433
253
|
|
|
434
|
-
|
|
435
|
-
|
|
436
254
|
return {
|
|
437
255
|
Table,
|
|
438
|
-
|
|
256
|
+
FlameTablePopup,
|
|
439
257
|
ColumnNames,
|
|
440
258
|
add,
|
|
441
259
|
edit,
|
|
442
|
-
SaveTable,
|
|
443
260
|
deleteRow,
|
|
444
|
-
fileUpdated,
|
|
445
|
-
getSelector,
|
|
446
261
|
primaryKey,
|
|
447
262
|
columnClick,
|
|
448
263
|
isRowCollapsed,
|
|
449
264
|
MainTableElement,
|
|
450
|
-
|
|
451
|
-
// TODO: проверить
|
|
452
|
-
popupErrors,
|
|
453
|
-
|
|
454
|
-
PopupTextSaved
|
|
455
265
|
}
|
|
456
266
|
|
|
457
267
|
},
|
|
@@ -487,40 +297,6 @@ export default defineComponent({
|
|
|
487
297
|
|
|
488
298
|
},
|
|
489
299
|
|
|
490
|
-
fileGetData(thisArr: any, getSource: boolean = true) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (thisArr instanceof Array && thisArr.length > 0) {
|
|
495
|
-
// Получить строку с исходником
|
|
496
|
-
if (getSource) {
|
|
497
|
-
if (typeof thisArr[0].data === 'string') return thisArr[0].data;
|
|
498
|
-
if (typeof thisArr[0].file === 'string') return REST.SERVER + "/" + thisArr[0].file;
|
|
499
|
-
return "";
|
|
500
|
-
}
|
|
501
|
-
// Получить данные целиком
|
|
502
|
-
return thisArr[0]
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return null
|
|
506
|
-
|
|
507
|
-
},
|
|
508
|
-
|
|
509
|
-
getFileName(fileModel: any) {
|
|
510
|
-
const res = this.fileGetData(fileModel, false)
|
|
511
|
-
if (res?.name) {
|
|
512
|
-
if (typeof res.name === 'string' && res.name !== '') {
|
|
513
|
-
return res.name;
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
if (res.file) {
|
|
517
|
-
const splitted = res.file.split('.');
|
|
518
|
-
return 'File.' + splitted[splitted.length - 1];
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
|
|
524
300
|
CheckboxSelectionChanged(event: any, all: boolean = false) {
|
|
525
301
|
if (all === true)
|
|
526
302
|
for (const key in this.Table.RowsParams) {
|
|
@@ -529,7 +305,7 @@ export default defineComponent({
|
|
|
529
305
|
|
|
530
306
|
this.Table.getSelectedRows();
|
|
531
307
|
|
|
532
|
-
// ТУТ СОБЫТИЕ ОБ ИЗМЕНЕНИИ ПРИХОДИТ В ИСТАНС
|
|
308
|
+
// ТУТ СОБЫТИЕ ОБ ИЗМЕНЕНИИ ПРИХОДИТ В ИСТАНС РАНШЬЕ САМОГО ИЗМЕНЕНИЯ МАССИВА
|
|
533
309
|
|
|
534
310
|
}
|
|
535
311
|
},
|
|
@@ -1,60 +1,98 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<v-row
|
|
4
|
-
<v-col v-for="(col) in
|
|
3
|
+
<v-row no-gutters align="stretch">
|
|
4
|
+
<v-col v-for="(col) in sortedFilters" :key="col.name" v-show="col.Filter.isShow"
|
|
5
5
|
:class="(col.Filter.classes + (col.Filter.type === 'selector' && col.Filter.selector.mode === 'horizontal' ? ' w-full ' : ''))"
|
|
6
|
-
class="
|
|
6
|
+
class="pa-1" cols="12" md="auto" :style="getTextFilterStyle(col)">
|
|
7
7
|
|
|
8
8
|
<div class="fc flex-col h-100">
|
|
9
9
|
<div :class="col.Filter.titleClasses" class="text-xs text-slate-400 mb-1">{{ col.Filter.title ??
|
|
10
10
|
col.title.toUpperCase() ?? col.name }}</div>
|
|
11
|
+
<v-spacer v-if="mdAndUp"></v-spacer>
|
|
11
12
|
|
|
12
13
|
<!-- ТЕКСТ -->
|
|
13
|
-
<div class="
|
|
14
|
+
<div class="w-full"
|
|
14
15
|
v-if="col.Filter.type === 'text' || col.Filter.type === 'fixed' || col.Filter.type === 'fulltext'">
|
|
15
16
|
<v-text-field density="compact" :label="col.title ?? col.name" type="text"
|
|
16
|
-
@update:model-value="update()" v-model="col.Filter.valueString" hide-details />
|
|
17
|
+
@update:model-value="update()" v-model="col.Filter.valueString" hide-details variant="filled" />
|
|
17
18
|
</div>
|
|
18
19
|
|
|
19
20
|
<!-- ЦИФРЫ -->
|
|
20
21
|
<v-row dense v-if="col.Filter.type === 'number'">
|
|
21
|
-
<v-col
|
|
22
|
-
<v-text-field density="compact" :label="'От ' + (col.title ?? col.name)" type="number"
|
|
23
|
-
@update:model-value="update()" v-model="col.Filter.valueRangeNumbers.from" hide-details />
|
|
22
|
+
<v-col :style="getNumberFilterStyle(col)">
|
|
23
|
+
<v-text-field class="w-full" density="compact" :label="'От ' + (col.title ?? col.name)" type="number"
|
|
24
|
+
@update:model-value="update()" v-model="col.Filter.valueRangeNumbers.from" hide-details variant="filled" />
|
|
24
25
|
</v-col>
|
|
25
|
-
<v-col
|
|
26
|
-
<v-text-field density="compact" :label="'До ' + (col.title ?? col.name)" type="number"
|
|
27
|
-
@update:model-value="update()" v-model="col.Filter.valueRangeNumbers.to" hide-details />
|
|
26
|
+
<v-col :style="getNumberFilterStyle(col)">
|
|
27
|
+
<v-text-field class="w-full" density="compact" :label="'До ' + (col.title ?? col.name)" type="number"
|
|
28
|
+
@update:model-value="update()" v-model="col.Filter.valueRangeNumbers.to" hide-details variant="filled" />
|
|
28
29
|
</v-col>
|
|
29
30
|
</v-row>
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
<!-- СЕЛЕКТОРЫ -->
|
|
33
|
-
<div v-if="col.Filter.type === 'selector' && col.Filter.selector.mode !== 'horizontal'" class="
|
|
34
|
+
<div v-if="col.Filter.type === 'selector' && col.Filter.selector.mode !== 'horizontal'" class="w-full">
|
|
34
35
|
<!-- Multi-select -->
|
|
35
36
|
<v-select v-if="col.Filter.selector.multiselect" v-model="col.Filter.valueRange"
|
|
36
37
|
:label="col.title ?? col.name" :items="col.Selector.values" item-title="title" item-value="id"
|
|
37
38
|
class="mobile:w-full min-w-[200px]" density="compact" @update:model-value="update()" hide-details multiple
|
|
38
|
-
chips closable-chips>
|
|
39
|
+
chips closable-chips variant="filled">
|
|
40
|
+
<template #item="{ props, item }">
|
|
41
|
+
<v-list-item v-bind="props">
|
|
42
|
+
<template #prepend v-if="item.raw.prepend">
|
|
43
|
+
<span :class="item.raw.prependClasses">{{ item.raw.prepend }}</span>
|
|
44
|
+
</template>
|
|
45
|
+
<template #append v-if="item.raw.append">
|
|
46
|
+
<span :class="item.raw.appendClasses">{{ item.raw.append }}</span>
|
|
47
|
+
</template>
|
|
48
|
+
</v-list-item>
|
|
49
|
+
</template>
|
|
50
|
+
<template #chip="{ props, item }">
|
|
51
|
+
<v-chip v-bind="props">
|
|
52
|
+
<span v-if="item.raw.prepend" :class="item.raw.prependClasses" class="mr-1">{{ item.raw.prepend
|
|
53
|
+
}}</span>
|
|
54
|
+
<span>{{ item.raw.title }}</span>
|
|
55
|
+
<span v-if="item.raw.append" :class="item.raw.appendClasses" class="ml-1">{{ item.raw.append }}</span>
|
|
56
|
+
</v-chip>
|
|
57
|
+
</template>
|
|
39
58
|
</v-select>
|
|
40
59
|
<!-- Single-select -->
|
|
41
60
|
<v-select v-else v-model="col.Filter.valueString" :label="col.title ?? col.name"
|
|
42
61
|
:items="col.Selector.values" item-title="title" item-value="id" class="mobile:w-full min-w-[200px]"
|
|
43
|
-
density="compact" @update:model-value="update()" hide-details>
|
|
62
|
+
density="compact" @update:model-value="update()" hide-details variant="filled">
|
|
63
|
+
<template #item="{ props, item }">
|
|
64
|
+
<v-list-item v-bind="props">
|
|
65
|
+
<template #prepend v-if="item.raw.prepend">
|
|
66
|
+
<span :class="item.raw.prependClasses">{{ item.raw.prepend }}</span>
|
|
67
|
+
</template>
|
|
68
|
+
<template #append v-if="item.raw.append">
|
|
69
|
+
<span :class="item.raw.appendClasses">{{ item.raw.append }}</span>
|
|
70
|
+
</template>
|
|
71
|
+
</v-list-item>
|
|
72
|
+
</template>
|
|
73
|
+
<template #selection="{ item }">
|
|
74
|
+
<div class="d-flex align-center w-100">
|
|
75
|
+
<span v-if="item.raw.prepend" :class="item.raw.prependClasses" class="mr-1">{{ item.raw.prepend
|
|
76
|
+
}}</span>
|
|
77
|
+
<span class="font-normal">{{ item.raw.title }}</span>
|
|
78
|
+
<v-spacer></v-spacer>
|
|
79
|
+
<span v-if="item.raw.append" :class="item.raw.appendClasses">{{ item.raw.append }}</span>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
44
82
|
</v-select>
|
|
45
83
|
</div>
|
|
46
84
|
|
|
47
85
|
<!-- ГОРИЗОНТАЛЬНЫЕ -->
|
|
48
86
|
<div v-if="col.Filter.type === 'selector' && col.Filter.selector.mode === 'horizontal'"
|
|
49
87
|
class="w-full text-center">
|
|
50
|
-
<div v-for="item in col.Selector.values" :key="item.id" class="px-2 py-1 mx-1 inline-block rounded-[30px]"
|
|
88
|
+
<div v-for="item in col.Selector.values" :key="item.id?.toString()" class="px-2 py-1 mx-1 inline-block rounded-[30px]"
|
|
51
89
|
:style="'background-color:' + (item.color ?? 'rgb(148,163,184)')">
|
|
52
90
|
{{ item.title }}
|
|
53
91
|
</div>
|
|
54
92
|
</div>
|
|
55
93
|
|
|
56
94
|
<!-- ДАТА -->
|
|
57
|
-
<div class="
|
|
95
|
+
<div class="w-full" v-if="col.Filter.type === 'date' || col.Filter.type === 'daterange'">
|
|
58
96
|
<Datepicker class="mobile:w-full" v-if="col.Filter.type === 'date'" v-model="col.Filter.valueString"
|
|
59
97
|
:auto-apply="true" :enable-time-picker="false" @update:model-value="update()" :teleport="true"></Datepicker>
|
|
60
98
|
<Datepicker class="mobile:w-full" v-if="col.Filter.type === 'daterange'" v-model="col.Filter.valueRange"
|
|
@@ -70,7 +108,7 @@
|
|
|
70
108
|
</template>
|
|
71
109
|
|
|
72
110
|
<script setup lang="ts" generic="T">
|
|
73
|
-
import { onMounted, reactive, ref
|
|
111
|
+
import { onMounted, reactive, ref } from 'vue'; import { computed, type Ref } from 'vue'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST from "flamerest"
|
|
74
112
|
|
|
75
113
|
// Иконки
|
|
76
114
|
import { XCircleIcon } from '@icons/24/solid'
|
|
@@ -81,9 +119,12 @@ import FlameTable from './FlameTable';
|
|
|
81
119
|
import Datepicker from '@vuepic/vue-datepicker';
|
|
82
120
|
// import vSelect from 'vue-select' // Больше не используется
|
|
83
121
|
import ITableSelectorItem from './TableSelectorItem';
|
|
122
|
+
import { useDisplay } from 'vuetify';
|
|
123
|
+
import debounce from 'lodash-es/debounce';
|
|
84
124
|
|
|
85
125
|
// Глобальное хранилище и роуты
|
|
86
126
|
const store = storeFile(), router = useRouter(), route = useRoute();
|
|
127
|
+
const { mdAndUp } = useDisplay();
|
|
87
128
|
|
|
88
129
|
// Локальное состояние компонента
|
|
89
130
|
const state = reactive({
|
|
@@ -96,13 +137,42 @@ const props = defineProps<{
|
|
|
96
137
|
table: FlameTable<any>,
|
|
97
138
|
}>()
|
|
98
139
|
|
|
140
|
+
const sortedFilters = computed(() => {
|
|
141
|
+
return Object.values(props.columns).sort((a, b) => (a.Filter.position ?? 999) - (b.Filter.position ?? 999));
|
|
142
|
+
});
|
|
143
|
+
|
|
99
144
|
// Примонтировано
|
|
100
145
|
onMounted(async () => {
|
|
101
146
|
|
|
102
147
|
})
|
|
103
148
|
|
|
149
|
+
const getTextFilterStyle = (col: Column) => {
|
|
150
|
+
if (!mdAndUp.value || col.Filter.type === 'number') return '';
|
|
151
|
+
|
|
152
|
+
let width = col.Filter.width;
|
|
153
|
+
if (width === null || typeof width === 'undefined') {
|
|
154
|
+
if (['text', 'fixed', 'fulltext'].includes(col.Filter.type)) {
|
|
155
|
+
width = 250;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
104
158
|
|
|
105
|
-
|
|
159
|
+
if (width) {
|
|
160
|
+
return `min-width: ${width}px; max-width: ${width}px;`;
|
|
161
|
+
}
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const getNumberFilterStyle = (col: Column) => {
|
|
166
|
+
if (!mdAndUp.value) return '';
|
|
167
|
+
let width = col.Filter.width;
|
|
168
|
+
if (width === null || typeof width === 'undefined') {
|
|
169
|
+
width = 85;
|
|
170
|
+
}
|
|
171
|
+
return `min-width: ${width}px; max-width: ${width}px;`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
const update = debounce(() => {
|
|
106
176
|
|
|
107
177
|
// селектор преобразуем к v-model
|
|
108
178
|
/*
|
|
@@ -115,7 +185,7 @@ const update = () => {
|
|
|
115
185
|
|
|
116
186
|
props.table.update({}, null, 'filters');
|
|
117
187
|
|
|
118
|
-
}
|
|
188
|
+
}, 300);
|
|
119
189
|
|
|
120
190
|
|
|
121
191
|
|
package/src/Table/TableOpts.ts
CHANGED
|
@@ -76,6 +76,11 @@ export default class TableOpts {
|
|
|
76
76
|
// TODO: ЕСТЬ ОШИБКА???
|
|
77
77
|
if (this.columnsOpts[ColumnName] === undefined) this.columnsOpts[ColumnName] = new Column as any
|
|
78
78
|
merge(this.columnsOpts[ColumnName], mergingOpts)
|
|
79
|
+
|
|
80
|
+
// Если у нас есть ссылка на таблицу, переинициализируем селектор для этой колонки
|
|
81
|
+
if ((this as any)._tableInstance) {
|
|
82
|
+
(this as any)._tableInstance.reinitSelectorForColumn(ColumnName)
|
|
83
|
+
}
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
/**
|