flameresttable 1.0.38 → 1.0.39

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flameresttable",
3
- "version": "1.0.38",
3
+ "version": "1.0.39",
4
4
  "main": "./src/plugin.ts",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,8 +15,8 @@
15
15
  "android": "npm run build && npx cap add android && npx cap sync && npx cap open android"
16
16
  },
17
17
  "dependencies": {
18
- "@vuepic/vue-datepicker": "^7.2.0",
19
- "flamerest": "^1.0.93",
18
+ "@vuepic/vue-datepicker": "^7.2.1",
19
+ "flamerest": "^1.0.94",
20
20
  "lodash-es": "^4.17.21",
21
21
  "pinia": "^2.1.7",
22
22
  "pug": "github:FlameArt/pug-tailwind-only",
@@ -38,7 +38,7 @@
38
38
  "@volar/vue-language-plugin-pug": "^1.6.5",
39
39
  "@vue/compiler-sfc": "^3.3.7",
40
40
  "autoprefixer": "^10.4.16",
41
- "eslint": "^8.52.0",
41
+ "eslint": "^8.53.0",
42
42
  "eslint-plugin-vue": "^8.7.1",
43
43
  "postcss": "^8.4.31",
44
44
  "postcss-import": "^14.1.0",
package/src/App.vue CHANGED
@@ -54,6 +54,7 @@ opts.LoadParams.sort = ['-dt1'];
54
54
  Table(:model="Model", :opts="opts")
55
55
  template(v-slot:RowSubSlot="params")
56
56
  | {{ params.row.name + ' LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG LOOOONG line' }}
57
+ template(#otherButtons)
57
58
  </template>
58
59
 
59
60
  <style>
@@ -125,12 +125,17 @@ export default class FlameTable<T> {
125
125
 
126
126
  // удаляем чтобы при перезагрузке не сохранялось состояние пред. строк
127
127
  // производительность: если не стирать параметры строк, то при долгом пользовании таблицей и больших её объёмах в памяти накопится куча позиций, это утечка
128
- for (const key in this.RowsParams) delete this.RowsParams[key];
128
+
129
+ // upd: Не удаляем, чтобы хранить данные по выделениям между фильтрацией и паджинацией. Сбрасывается вручную
130
+ for (const key in this.RowsParams)
131
+ if (!this.RowsParams[key].selected)
132
+ delete this.RowsParams[key];
129
133
 
130
134
  // Заполняем параметры каждой строки
131
135
  this.Rows.rows.forEach(row => {
132
- // @ts-expect-error okay
133
- this.RowsParams[row[this.model.constructor.primaryKeys[0]]] = new TableRowParams
136
+ const newParam = new TableRowParams;
137
+ newParam.item = row;
138
+ this.RowsParams[(row as any)[(this.model as any).constructor.primaryKeys[0]]] = newParam;
134
139
  })
135
140
  }
136
141
 
@@ -194,14 +199,12 @@ export default class FlameTable<T> {
194
199
 
195
200
  /**
196
201
  * Обновить результаты от RESTа
197
- * @param SaveLoadParams Сохранить стандартные параметры загрузки
202
+ * @param CustomLoadParams Применить кастомные параметры загрузки
198
203
  */
199
- public async update(SaveLoadParams: any = null) {
200
-
201
- if (SaveLoadParams !== null) this.opts.LoadParams = SaveLoadParams;
204
+ public async update(CustomLoadParams: ITableLoadParams = {}) {
202
205
 
203
206
  // Дополняем загрузочные параметры фильтрами
204
- const filters = this.applyFiltersParams();
207
+ const filters = merge(this.applyFiltersParams(), CustomLoadParams);
205
208
 
206
209
  const rows: Rows<T> = await (this.model as any).constructor.all(merge({}, this.opts.LoadParams, filters, { page: this.Pager.page, perPage: this.Pager.perPage }));
207
210
 
@@ -209,6 +212,10 @@ export default class FlameTable<T> {
209
212
 
210
213
  }
211
214
 
215
+ /**
216
+ * Применить параметры, указанные в фильтрах
217
+ * @returns
218
+ */
212
219
  public applyFiltersParams() {
213
220
  const customFilters: ITableLoadParams = {
214
221
  where: {},
@@ -227,9 +234,11 @@ export default class FlameTable<T> {
227
234
  customFilters.params[key] = el.valueString;
228
235
  continue;
229
236
  case 'daterange':
230
- case 'number':
231
237
  customFilters.params[key] = el.valueRange;
232
238
  continue;
239
+ case 'number':
240
+ customFilters.params[key] = el.valueRangeNumbers;
241
+ continue;
233
242
  case 'selector':
234
243
  if (this.columns[key].Filter.selector.multiselect) {
235
244
  customFilters.params[key] = el.valueRange;
@@ -381,22 +390,20 @@ export default class FlameTable<T> {
381
390
  return res;
382
391
  }
383
392
 
393
+ /**
394
+ * Получить все выбранные колонки массивом объектов строк
395
+ * @returns
396
+ */
384
397
  public getSelectedRows() {
385
- const result: Array<T> = [];
386
- this.Rows.rows.forEach((r: any) => {
387
- const param = this.RowsParams[r[(this.model as any).constructor.primaryKeys[0]]]
388
- if (param.selected)
389
- result.push(r)
390
- })
391
- return result;
398
+ return this.Rows.rows.filter((r: any) => r.selected).map((r: any) => r.item);
392
399
  }
393
400
 
394
401
  /**
395
- * Выгрузить существующие колонки в CSV
402
+ * Выгрузить существующие колонки в CSV средствами браузера
396
403
  * @param filename
397
404
  * @param arr
398
405
  */
399
- public exportSelectedToCSV(filename: string = "table.csv", arr: any = null) {
406
+ public exportToCSV(filename: string = "table.csv", arr: any = null) {
400
407
 
401
408
  arr = arr ?? this.getSelectedRows();
402
409
 
@@ -412,4 +419,59 @@ export default class FlameTable<T> {
412
419
 
413
420
  }
414
421
 
422
+ /**
423
+ * Выгрузить в Excel средствами бекенда
424
+ * @param filename
425
+ * @param arr
426
+ */
427
+ public exportToXLS(onlySelected: boolean = false, filename: string | null = null, arr: any = null, columnList: Array<string> | null = null) {
428
+
429
+ const tClass = Object.getPrototypeOf(this.model).constructor;
430
+ const indexKey = tClass.primaryKeys[0];
431
+
432
+ const customFilter: ITableLoadParams = {
433
+ where: {}
434
+ };
435
+
436
+ // Только выбранные элементы
437
+ if (onlySelected) {
438
+ // Получаем айдишки и затем вносим их в условие IN
439
+ arr = arr ?? this.getSelectedRows();
440
+ const INArray: Array<any> = [];
441
+ for (const row of arr) {
442
+ if (typeof row[indexKey] !== 'undefined') {
443
+ INArray.push(row[indexKey]);
444
+ }
445
+ }
446
+ if (INArray.length > 0) {
447
+ customFilter.where = {
448
+ indexKey: ['IN', indexKey, INArray]
449
+ }
450
+ }
451
+ }
452
+
453
+ customFilter.export = {
454
+ format: "xlsx",
455
+ titles: [],
456
+ filename: filename ?? 'export_' + Date.now() + ".xlsx"
457
+ };
458
+ customFilter.fields = [];
459
+
460
+ for (const colKey in this.columns) {
461
+
462
+ const col = this.columns[colKey];
463
+ if (columnList !== null && (!columnList.includes(col.name))) continue;
464
+ if (columnList === null && (col.isVirtual || !col.Table.isShow)) continue;
465
+
466
+ // проверки пройдены, все оставшиеся колонки добавляем в вывод
467
+ (customFilter as any).export.titles.push(col.title !== '' ? col.title : col.name);
468
+ customFilter.fields.push(col.name);
469
+
470
+ }
471
+
472
+ // Делаем запрос на отдачу
473
+ this.update(customFilter);
474
+
475
+ }
476
+
415
477
  }
@@ -4,40 +4,18 @@
4
4
  <!-- ФИЛЬТРЫ -->
5
5
  <TableFilters :columns="Table.columns" :table="Table"></TableFilters>
6
6
 
7
- <!-- "ДОБАВИТЬ" И ЧИСЛО ЗАПИСЕЙ-->
8
- <div class="flex items-center justify-between">
9
- <slot name="defaultButtons">
10
- <div v-show="$props.opts?.Add.can"
11
- class="flex mr-3 cursor-pointer justify-center items-center text-white bg-blue-600 font-medium rounded text-sm px-4 text-center hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900"
12
- @click="add()">
13
- <div class="text-xl">
14
- +
15
- </div>
16
- <div class="ml-2">
17
- {{ $props.opts?.Add.buttonTitle }}
18
- </div>
19
- </div>
20
- <div v-show="$props.opts?.Export.CSV.can"
21
- class="flex mr-3 cursor-pointer justify-center items-center text-white bg-blue-600 font-medium rounded text-sm px-4 text-center hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900"
22
- @click="Table.exportSelectedToCSV()">
23
- <div class="text-xl">
24
- +
25
- </div>
26
- <div class="ml-2">
27
- {{ $props.opts?.Export.CSV.buttonTitle }}
28
- </div>
29
- </div>
30
- </slot>
31
- <slot name="otherButtons" />
32
-
33
- <!-- ЧИСЛО ЗАПИСЕЙ -->
34
- <div class="text-xs text-gray-400">
35
- {{ opts.lang === 'ru' ? 'Показано:' : 'Showing' }} {{ Table.Pager.total > Table.Pager.perPage * Table.Pager.page ?
36
- Table.Pager.perPage * Table.Pager.page : Table.Pager.total }} / {{ Table.Pager.total }} {{ opts.lang === 'ru' ?
37
- 'записей' : ' records' }}
38
- </div>
39
-
40
- </div>
7
+ <!-- ПАНЕЛЬ ОПЕРАЦИЙ НАД ТАБЛИЦЕЙ -->
8
+ <TableTasksPanel :table="Table" @add="add">
9
+ <template #defaultButtons>
10
+ <slot name="defaultButtons">
11
+ </slot>
12
+ </template>
13
+ <template #otherButtons>
14
+ <slot name="otherButtons">
15
+ </slot>
16
+ </template>
17
+
18
+ </TableTasksPanel>
41
19
 
42
20
  <!-- ТАБЛИЦА-->
43
21
  <div class="mt-2 table table-fixed w-full ">
@@ -72,7 +50,7 @@
72
50
  </div>
73
51
 
74
52
  <!-- СТРОКИ-->
75
- <div class="table-row-group">
53
+ <div class="table-row-group text-left">
76
54
 
77
55
  <template v-for="(row) in (Table.Rows.rows)">
78
56
  <div :key="'row_' + (row as any).id" class="defaultRow table-row cursor-pointer hover:bg-slate-100" v-if="true">
@@ -248,10 +226,11 @@ import Datepicker from '@vuepic/vue-datepicker';
248
226
  import '@vuepic/vue-datepicker/dist/main.css';
249
227
  import { isNumber } from 'lodash';
250
228
  import 'vue-select/dist/vue-select.css';
229
+ import TableTasksPanel from './TableTasksPanel.vue';
251
230
 
252
231
 
253
232
  export default defineComponent({
254
- components: { ModalVue, Paginator, Datepicker, TableFilters },
233
+ components: { ModalVue, Paginator, Datepicker, TableFilters, TableTasksPanel },
255
234
  props: {
256
235
  rows: {
257
236
  default: [] as Array<any>,
@@ -1,11 +1,17 @@
1
1
  export default interface ITableLoadParams {
2
2
 
3
3
  where?: any
4
- fields?: any,
5
- expand?: any,
4
+ fields?: Array<string>,
5
+ expand?: Array<any>,
6
6
  sort?: Array<string>,
7
7
  page?: number,
8
8
  perPage?: number,
9
- params?: any
9
+ params?: any,
10
+
11
+ export?: {
12
+ format: 'xlsx' | 'csv',
13
+ titles?: Array<string>,
14
+ filename?: string,
15
+ }
10
16
 
11
17
  }
@@ -2,5 +2,6 @@ export default class TableRowParams {
2
2
 
3
3
  public collapsed: boolean = true;
4
4
  public selected: boolean = false;
5
+ public item: any = null;
5
6
 
6
7
  }
@@ -0,0 +1,192 @@
1
+ <template>
2
+ <!-- "ДОБАВИТЬ" И ЧИСЛО ЗАПИСЕЙ-->
3
+ <div class="flex flex-wrap items-center justify-between">
4
+
5
+ <!-- ЛЕВАЯ ЧАСТЬ -->
6
+ <div>
7
+ <!-- Стандартные кнопки -->
8
+ <slot name="defaultButtons">
9
+ <div v-show="state.opts?.Add.can"
10
+ class="flex mr-3 cursor-pointer justify-center items-center text-white bg-blue-600 font-medium rounded text-sm px-4 text-center hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900"
11
+ @click="emit('add')">
12
+ <div class="text-xl">
13
+ +
14
+ </div>
15
+ <div class="ml-2">
16
+ {{ state.opts?.Add.buttonTitle }}
17
+ </div>
18
+ </div>
19
+ <div v-show="state.opts?.Export.CSV.can"
20
+ class="flex mr-3 cursor-pointer justify-center items-center text-white bg-blue-600 font-medium rounded text-sm px-4 text-center hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900"
21
+ @click="state.Table.exportToCSV()">
22
+ <div class="text-xl">
23
+ +
24
+ </div>
25
+ <div class="ml-2">
26
+ {{ state.opts?.Export.CSV.buttonTitle }}
27
+ </div>
28
+ </div>
29
+ </slot>
30
+
31
+ <!-- Доп кнопки -->
32
+ <slot name="otherButtons" />
33
+
34
+
35
+ <!-- ЧИСЛО ЗАПИСЕЙ -->
36
+ <div class="text-xs text-gray-400">
37
+ {{ state.opts.lang === 'ru' ? 'Показано:' : 'Showing' }} {{ state.Table.Pager.total > state.Table.Pager.perPage *
38
+ state.Table.Pager.page ?
39
+ state.Table.Pager.perPage * state.Table.Pager.page : state.Table.Pager.total }} / {{ state.Table.Pager.total }} {{
40
+ state.opts.lang === 'ru' ?
41
+ 'записей' : ' records' }}
42
+ </div>
43
+
44
+ </div>
45
+
46
+ <!-- ПРАВАЯ ЧАСТЬ -->
47
+ <div>
48
+
49
+ <!-- ДЕЙСТВИЯ С ОТМЕЧЕННЫМИ -->
50
+ <div class="fc">
51
+ <div>
52
+ <label>
53
+ <select class="minimal text-xs text-center text-slate-500" v-model="state.selectModel" @change="selectAction">
54
+ <option :value="0">{{ state.opts.lang === 'ru' ? 'ДЕЙСТВИЯ' : 'ACTIONS' }}</option>
55
+ <option :value="1">{{ state.opts.lang === 'ru' ? 'Выгрузить выбранные' : 'Export selected' }} [{{ 5 }}]
56
+ </option>
57
+ <option :value="2">{{ state.opts.lang === 'ru' ? 'Выгрузить всё' : 'Export all' }} [{{
58
+ state.Table.Pager.total }}]</option>
59
+ </select>
60
+ </label>
61
+ </div>
62
+ <!--
63
+ <div
64
+ class="flex mr-3 cursor-pointer justify-center items-center text-white bg-blue-600 font-medium rounded text-sm px-4 text-center hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900">
65
+ <div class="text-xl">
66
+ +
67
+ </div>
68
+ <div class="ml-2">
69
+ Экспорт отмеченных {{ 5 }}
70
+ </div>
71
+ </div>
72
+ <div
73
+ class="flex mr-3 cursor-pointer justify-center items-center text-white bg-blue-600 font-medium rounded text-sm px-4 text-center hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900">
74
+ <div class="text-xl">
75
+ +
76
+ </div>
77
+ <div class="ml-2">
78
+ Экспорт всех {{ state.Table.Pager.total }}
79
+ </div>
80
+ </div>
81
+ -->
82
+ </div>
83
+ </div>
84
+
85
+ </div>
86
+ </template>
87
+
88
+ <script setup lang="ts">
89
+ import { onMounted, reactive, ref, defineProps, defineEmits, nextTick } from '@vue/runtime-core'; import type { Ref } from 'vue'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST from "flamerest"
90
+
91
+ // Иконки
92
+ import { } from '@icons/24/solid'
93
+ import FlameTable from './FlameTable';
94
+
95
+ // Глобальное хранилище и роуты
96
+ const store = storeFile(), router = useRouter(), route = useRoute();
97
+
98
+ // Входящие данные компонента
99
+ const props = defineProps<{
100
+ table: FlameTable<any>,
101
+ }>()
102
+ const emit = defineEmits(['add'])
103
+
104
+ // Локальное состояние компонента
105
+ const state = reactive({
106
+ opts: props.table.opts,
107
+ Table: props.table,
108
+ selectModel: 0
109
+ })
110
+
111
+ const selectAction = () => {
112
+
113
+ switch (state.selectModel) {
114
+ case 0:
115
+ return;
116
+ case 1:
117
+
118
+ return;
119
+ case 2:
120
+ state.Table.exportToXLS();
121
+ return;
122
+ }
123
+
124
+ }
125
+
126
+ </script>
127
+
128
+ <style scoped lang="scss">
129
+ $height: 36;
130
+
131
+ label {
132
+ position: relative;
133
+ display: inline-block;
134
+
135
+ &:before {
136
+ content: '';
137
+ height: ($height - 5) + px;
138
+ position: absolute;
139
+ right: 7px;
140
+ top: 3px;
141
+ width: 22px;
142
+
143
+ //background: -webkit-linear-gradient(#fff, #f0f0f0);
144
+ //background: -moz-linear-gradient(#fff, #f0f0f0);
145
+ //background: linear-gradient(#f5f5f5, #e0e0e0);
146
+ background: #fff; //for Firefox in Android
147
+
148
+ border-top-right-radius: 3px;
149
+ border-bottom-right-radius: 3px;
150
+ pointer-events: none;
151
+ display: block;
152
+ }
153
+
154
+ &:after {
155
+ content: " ";
156
+ position: absolute;
157
+ right: 15px;
158
+ top: 46%;
159
+ margin-top: -3px;
160
+ z-index: 2;
161
+ pointer-events: none;
162
+ width: 0;
163
+ height: 0;
164
+ border-style: solid;
165
+ border-width: 6.9px 4px 0 4px;
166
+ border-color: #aaa transparent transparent transparent;
167
+ pointer-events: none;
168
+ }
169
+
170
+ select {
171
+ -webkit-appearance: none;
172
+ -moz-appearance: none;
173
+ appearance: none;
174
+ padding: 0 30px 0 10px;
175
+
176
+ border: 1px solid #e0e0e0;
177
+ border-radius: 3px;
178
+ line-height: $height + px;
179
+ height: $height + px;
180
+ //box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.2);
181
+ background: #fff;
182
+
183
+ //min-width: 200px;
184
+ margin: 0 5px 5px 0;
185
+ }
186
+ }
187
+
188
+ //fix for ie 10 later
189
+ select::-ms-expand {
190
+ display: none;
191
+ }
192
+ </style>
@@ -1,47 +0,0 @@
1
- <template>
2
- <!-- Это селектор c мультивыбором -->
3
- <div>
4
-
5
- </div>
6
- </template>
7
-
8
- <script setup lang="ts" generic="T">
9
- import { onMounted, reactive, ref, defineProps } from '@vue/runtime-core'; import type { Ref } from 'vue'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST from "flamerest"
10
-
11
- // Иконки
12
- import { XCircleIcon } from '@icons/24/solid'
13
- import TableOpts from './TableOpts';
14
- import { Column } from './Columns';
15
- //import { TableFilter } from './TableOpts.js';
16
- import FlameTable from './FlameTable';
17
- import Datepicker from '@vuepic/vue-datepicker';
18
-
19
- // Глобальное хранилище и роуты
20
- const store = storeFile(), router = useRouter(), route = useRoute();
21
-
22
- // Локальное состояние компонента
23
- const state = reactive({
24
- data: {}
25
- })
26
-
27
- // Входящие данные компонента
28
- const props = defineProps<{
29
- columns: { [key: string]: Column; },
30
- table: FlameTable<any>,
31
- }>()
32
-
33
- // Примонтировано
34
- onMounted(async () => {
35
-
36
- })
37
-
38
-
39
- const update = () => {
40
-
41
- props.table.update();
42
-
43
- }
44
-
45
- </script>
46
-
47
- <style scoped lang="scss"></style>