flameresttable 1.0.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.
@@ -0,0 +1,210 @@
1
+ import { Column } from './Columns';
2
+ import { Rows, SavedObject } from 'flamerest';
3
+ import TableOpts from "./TableOpts";
4
+ import { reactive, UnwrapRef } from 'vue';
5
+ import merge from 'lodash.merge';
6
+
7
+ // Подгрузчик типа класса
8
+ type Class<T> = new (...args: any[]) => T
9
+
10
+ /**
11
+ * Объект с состоянием REST таблицы
12
+ */
13
+ export default class FlameTable<T> {
14
+
15
+ /**
16
+ * Режим таблицы
17
+ */
18
+ public mode: "table" | "add" | "edit" = "table"
19
+
20
+ /**
21
+ * Готовый набор колонок [уже обработанный]
22
+ * key - имя колонки, Column - её опции
23
+ */
24
+ public columns: { [key: string]: Column } = reactive({});
25
+
26
+ /**
27
+ * Параметры таблицы
28
+ */
29
+ public opts: TableOpts = new TableOpts;
30
+
31
+ /**
32
+ * Инициализированная модель REST-таблицы
33
+ */
34
+ public model: T;
35
+
36
+ /**
37
+ * Строки
38
+ */
39
+ public Rows = reactive({ rows: [] as Array<T> });
40
+
41
+ /**
42
+ * Параметры паджинации
43
+ */
44
+ public Pager = reactive({
45
+ page: 1,
46
+ perPage: 20,
47
+ count: 0,
48
+ total: 0,
49
+ })
50
+
51
+ /**
52
+ * Параметры прогрузки
53
+ */
54
+ public LoadParams = {} as any;
55
+
56
+ /**
57
+ * Открытая в попапе строка
58
+ */
59
+ public OpenedRow: any = null;
60
+
61
+ /**
62
+ * Проинициализировать модели
63
+ */
64
+ public constructor(TableModel: Class<T>, opts: TableOpts) {
65
+
66
+ // Инициализируем переданный класс
67
+ this.model = new TableModel;
68
+
69
+ // забираем все колонки из модели и из опций
70
+ // так мы можем вставлять люое число колонок не привязываясь
71
+ // удалим так же дубликаты
72
+ // TODO: as any
73
+ const allColumns = [...Object.keys(this.model as any), ...Object.keys(opts.columnsOpts)].filter((value, index, self) => self.indexOf(value) === index)
74
+
75
+ // генерим полноценные колонки из описания модели
76
+ for (const key of allColumns) {
77
+ const newCol = new Column;
78
+ this.columns[key] = newCol;
79
+
80
+ // Выключаем из редактирования все праймари ключи
81
+ if ((this.model as any).constructor['primaryKeys'].includes(key))
82
+ newCol.Popup.isEnabled = false
83
+
84
+ // Мержим стандартные параметры с опциями юзера
85
+ merge(newCol, opts.columnsOpts[key]);
86
+
87
+ // Жёстко устанавливаем название поля из базы для этой колонки
88
+ newCol.name = key;
89
+
90
+ // Загружаем селекторы один раз за страницу
91
+ if (newCol.Popup.popupType === 'selector' && newCol.Popup.Selector.loader !== null)
92
+ newCol.Popup.Selector.values = reactive(newCol.Popup.Selector.loader(newCol) ?? newCol.Popup.Selector.values)
93
+
94
+ }
95
+
96
+ // Мержим параметры с базовой версией
97
+ merge(this.opts, opts);
98
+
99
+ // TODO: Сортируем колонки, если указан порядок
100
+ //this.columns
101
+
102
+
103
+
104
+ // Далее юзер просто будет делать set("колонка", опции)
105
+
106
+ }
107
+
108
+ /**
109
+ * Загрузить результаты от RESTа в таблицу
110
+ * @param rows Отданный объект
111
+ */
112
+ public load(rows: Rows<T>) {
113
+
114
+ if (rows.data) {
115
+ this.Rows.rows = (rows.data ?? []) as any;
116
+ Object.keys(this.Pager).forEach((key) => (this.Pager as any)[key] = (rows.pages as any)[key])
117
+ }
118
+
119
+ }
120
+
121
+ /**
122
+ * Обновить результаты от RESTа
123
+ * @param SaveLoadParams Сохранить стандартные параметры загрузки
124
+ */
125
+ public async update(SaveLoadParams: any = null) {
126
+
127
+ if (SaveLoadParams !== null) this.LoadParams = SaveLoadParams;
128
+
129
+ const rows: Rows<T> = await (this.model as any).constructor.all(Object.assign(this.LoadParams, { page: this.Pager.page, perPage: this.Pager.perPage }));
130
+
131
+ this.load(rows);
132
+
133
+ }
134
+
135
+
136
+
137
+ public async add(): Promise<SavedObject<T> | null> {
138
+
139
+ // Сперва предобработка
140
+ // TODO: убрать пустой массив т.к. при добавлении он не нужен или прикрепить реактивную модель?
141
+ if (await this.opts.Popup.beforeAdd({}, this) === false) { return null; }
142
+
143
+ const res: SavedObject<T> = await Object.getPrototypeOf(this.model).constructor.create(this.getColumnsForUpdate('add'));
144
+
145
+ if (!res.ok || !res.data) {
146
+ throw res;
147
+ }
148
+
149
+ this.Rows.rows.push(reactive(res.data as any))
150
+
151
+ return res;
152
+
153
+ }
154
+
155
+ public async save(): Promise<SavedObject<T> | null> {
156
+
157
+ const tClass = Object.getPrototypeOf(this.model).constructor;
158
+ const columns = this.getColumnsForUpdate('edit');
159
+ const indexKey = tClass.primaryKeys[0];
160
+
161
+ // Сперва предобработка
162
+ if (await this.opts.Popup.beforeEdit(columns, this) === false) { return null; }
163
+
164
+ // Сохраняем
165
+ const res: SavedObject<T> = await tClass.edit(columns[indexKey], columns);
166
+
167
+ if (!res.ok) {
168
+ throw res;
169
+ }
170
+
171
+ if (res.data) {
172
+ const findedIdx = this.Rows.rows.findIndex((row: any) => row[indexKey] === (res.data as any)[indexKey])
173
+ if (findedIdx !== -1)
174
+ this.Rows.rows[findedIdx] = reactive(res.data as any);
175
+ }
176
+
177
+ return res;
178
+
179
+ }
180
+
181
+
182
+ public async remove(row: T): Promise<SavedObject<T>> {
183
+
184
+ const tClass = Object.getPrototypeOf(this.model).constructor;
185
+ const columns = this.getColumnsForUpdate('edit');
186
+ const indexKey = tClass.primaryKeys[0];
187
+ const res = await tClass.delete((row as any)[indexKey]);
188
+
189
+ const findedIdx = this.Rows.rows.findIndex((rowx: any) => rowx[indexKey] === (row as any)[indexKey])
190
+ if (findedIdx !== -1)
191
+ this.Rows.rows.splice(findedIdx, 1);
192
+
193
+ return res;
194
+
195
+ }
196
+
197
+ private getColumnsForUpdate(mode: "add" | "edit") {
198
+ const res: { [key: string]: string } = {};
199
+ for (const key in this.columns) {
200
+ if (mode === 'add' && !this.columns[key].Popup.isSendFromAdd) continue;
201
+ if (mode === 'edit' && !this.columns[key].Popup.isSendFromEdit) continue;
202
+ // TODO: здесь может быть полезно сохранять пустую строку
203
+ if (this.columns[key].Popup.model !== '') { res[key] = this.columns[key].Popup.model }
204
+ }
205
+ return res;
206
+ }
207
+
208
+
209
+
210
+ }
@@ -0,0 +1,55 @@
1
+ <template lang="pug">
2
+ nav.relative.z-0.w-full.fc.rounded-md.-space-x-px(aria-label='Pagination' @click="")
3
+ a.relative.inline-flex.items-center.px-2.py-2.rounded-l-md.border.border-gray-300.bg-white.text-sm.font-medium.text-gray-500(href='#' class='hover:bg-gray-50')
4
+ span.sr-only Previous
5
+ // Heroicon name: solid/chevron-left
6
+ svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' viewbox='0 0 20 20' fill='currentColor' aria-hidden='true')
7
+ path(fill-rule='evenodd' d='M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z' clip-rule='evenodd')
8
+ // Current: "z-10 bg-indigo-50 border-indigo-500 text-indigo-600", Default: "bg-white border-gray-300 text-gray-500 hover:bg-gray-50"
9
+ a.bg-white.border-gray-300.text-gray-500.relative.inline-flex.items-center.px-4.py-2.border.text-sm.font-medium(v-for="page in props.table.Pager.count" class="hover:bg-gray-50" :class="props.table.Pager.page === page ? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600' : ''" @click="goPage(page)")
10
+ | {{ page }}
11
+ //span.relative.inline-flex.items-center.px-4.py-2.border.border-gray-300.bg-white.text-sm.font-medium.text-gray-700 ...
12
+ a.relative.inline-flex.items-center.px-2.py-2.rounded-r-md.border.border-gray-300.bg-white.text-sm.font-medium.text-gray-500(href='#' class='hover:bg-gray-50')
13
+ span.sr-only Next
14
+ // Heroicon name: solid/chevron-right
15
+ svg.h-5.w-5(xmlns='http://www.w3.org/2000/svg' viewbox='0 0 20 20' fill='currentColor' aria-hidden='true')
16
+ path(fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd')
17
+
18
+
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ 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"
23
+
24
+ // Иконки
25
+ import { XCircleIcon } from '@icons/24/solid'
26
+ import FlameTable from './FlameTable';
27
+
28
+ // Глобальное хранилище и роуты
29
+ const store = storeFile(), router = useRouter(), route = useRoute();
30
+
31
+ // Локальное состояние компонента
32
+ const state = reactive({
33
+ data: {}
34
+ })
35
+
36
+ // Входящие данные компонента
37
+ const props = defineProps<{
38
+ table: typeof FlameTable.prototype
39
+ }>()
40
+
41
+
42
+ // Примонтировано
43
+ onMounted(async () => {
44
+
45
+ })
46
+
47
+ const goPage = (page: number) => {
48
+ // eslint-disable-next-line vue/no-mutating-props
49
+ props.table.Pager.page = page;
50
+ props.table.update();
51
+ }
52
+
53
+ </script>
54
+
55
+ <style scoped lang="scss"></style>
@@ -0,0 +1,189 @@
1
+ <template lang="pug">
2
+ .mt-0
3
+
4
+ // "ДОБАВИТЬ" И ЧИСЛО ЗАПИСЕЙ
5
+ .flex.items-center.justify-between
6
+ slot(name="defaultButtons")
7
+ .flex.cursor-pointer.justify-center.items-center.text-white.block.bg-blue-600.font-medium.rounded.text-sm.px-4.text-center(v-show="$props.opts?.Add.can" @click="add()" class="hover:bg-blue-700 .focus:ring-4.focus:ring-blue-200.dark:focus:ring-blue-900")
8
+ div.text-xl +
9
+ div.ml-2 {{ $props.opts?.Add.buttonTitle }}
10
+ slot(name="otherButtons")
11
+ .text-xs.text-gray-400 Показано: {{ Table.Pager.total > Table.Pager.perPage * Table.Pager.page ? Table.Pager.perPage * Table.Pager.page : Table.Pager.total }} / {{ Table.Pager.total }} записей
12
+
13
+ // ТАБЛИЦА
14
+ .mt-2.table.w-full
15
+
16
+ // ЗАГОЛОВКИ СТОЛБЦОВ
17
+ .table-header-group
18
+ .table-cell.border-r.border-r-slate-100.px-2(v-for="column in Table.columns" v-show="column.Table.isShow")
19
+ span {{ column.title !== '' ? column.title : column.name }}
20
+ .table-cell.border-r.border-r-slate-100.px-2(v-if="opts.Edit.can")
21
+ button.bg-green-600.invisible Изменить
22
+ .table-cell.border-r.border-r-slate-100.px-2(v-if="opts.Remove.can")
23
+ button.bg-green-600.invisible Удалить
24
+
25
+ // СТРОКИ
26
+ .table-row-group
27
+ .table-row.cursor-pointer(v-for="row in (Table.Rows.rows)" class="hover:bg-slate-100")
28
+ .table-cell.border-r.border-r-slate-100.px-2(v-for="column in Table.columns" v-show="column.Table.isShow" @click="column.Table.click(row, column)" class="last:border-r-0 last:pr-0")
29
+ span {{ column.Table.value(row, column) }}
30
+ .table-cell.border-r.border-r-slate-100.px-2.text-center(v-if="opts.Edit.can")
31
+ button.px-2.py-1.bg-green-600.text-white.my-1(@click="edit(row)") Изменить
32
+ .table-cell.border-r.border-r-slate-100.px-2.text-center(v-if="opts.Remove.can")
33
+ button.px-2.py-1.bg-gray-600.opacity-30.text-white.my-1(@click="deleteRow(row)") Удалить
34
+
35
+ Paginator.mt-3(:table="Table")
36
+
37
+ // МОДАЛКА ДОБАВЛЕНИЯ
38
+ ModalVue(ref="FlameTableModal")
39
+ template(v-slot)
40
+ .bg-white.rounded-xl(:class="'w-full desktop:w-[777px]'")
41
+
42
+ slot(name="header")
43
+
44
+ // КОЛОНКИ ТАБЛИЦЫ
45
+ label.flex.items-stretch.justify-center.w-full.my-1.ml-2(v-for="column in ColumnNames", v-show="Table.columns[column].Popup.isShow")
46
+ .w-44.text-left.px-2.py-1.bg-slate-100
47
+ div {{ Table.columns[column].Popup.title !== '' ? Table.columns[column].Popup.title === '' : Table.columns[column].title !== '' ? Table.columns[column].title : Table.columns[column].name }}
48
+ .text-xs.text-slate-400 {{ Table.columns[column].Popup.desc }}
49
+ .flex-1.mr-5.w-full.flex.flex-col.self-stretch(v-if="Table.columns[column].Popup.popupType === 'string' || Table.columns[column].Popup.popupType === 'text'")
50
+ input.h-full.self-stretch.py-1.px-2.w-full.outline-none.border.border-slate-100(v-model="Table.columns[column].Popup.model", type="text", :disabled="!Table.columns[column].Popup.isEnabled", :placeholder="Table.columns[column].Popup.placeholder !== '' ? Table.columns[column].Popup.placeholder : Table.columns[column].title !== '' ? Table.columns[column].title : Table.columns[column].name")
51
+ .flex-1.mr-5.w-full.flex.flex-col.self-stretch(v-if="Table.columns[column].Popup.popupType === 'date'")
52
+ Datepicker(v-model="Table.columns[column].Popup.model" autoApply )
53
+ .flex-1.mr-5.w-full.flex.flex-col.self-stretch(v-if="Table.columns[column].Popup.popupType === 'selector'")
54
+ select.h-full.self-stretch.py-1.px-2.w-full.outline-none.border.border-slate-100(v-model="Table.columns[column].Popup.model")
55
+ option(v-for="(selectorVal, selectorKey) in Table.columns[column].Popup.Selector.values" :value="getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[1]") {{ getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[0] }}
56
+
57
+ // КНОПКИ СОХРАНЕНИЯ
58
+ .flex.w-full.mt-3.mb-2.justify-between.items-center
59
+ .cursor-pointer.ml-2.text-lg.py-2.text-white.block.bg-gray-400.font-medium.rounded.text-sm.px-4.text-center(v-show="$props.opts?.Add.can" @click="Table.OpenedRow = null; FlameTableModal?.close()" class="hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-900") Отмена
60
+ .cursor-pointer.ml-2.text-lg.py-2.mr-3.text-white.block.bg-blue-600.font-medium.rounded.text-sm.px-4.text-center(@click="SaveTable()" class="hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-900") {{ Table.mode === 'add' ? 'Добавить' : 'Сохранить' }}
61
+
62
+
63
+
64
+ </template>
65
+
66
+ <script lang="ts">
67
+ 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, { Rows } from "flamerest"
68
+
69
+ // Иконки
70
+ import { XCircleIcon } from '@icons/24/solid'
71
+ import { computed } from '@vue/reactivity';
72
+ import { Column } from './Columns';
73
+ import { defineComponent } from 'vue';
74
+ import TableOpts from './TableOpts';
75
+ import FlameTable from './FlameTable';
76
+ import ModalVue from './../components/default/Modal.vue';
77
+ import Paginator from './Paginator.vue';
78
+ type Class<T> = new (...args: any[]) => T;
79
+
80
+ import Datepicker from '@vuepic/vue-datepicker';
81
+ import '@vuepic/vue-datepicker/dist/main.css';
82
+ import { isNumber } from 'lodash';
83
+
84
+
85
+ export default defineComponent({
86
+ components: { ModalVue, Paginator, Datepicker },
87
+ props: {
88
+ rows: {
89
+ default: [] as Array<any>,
90
+ type: Array<any>
91
+ },
92
+ opts: {
93
+ default: new TableOpts,
94
+ type: TableOpts
95
+ },
96
+ model: {
97
+ default: null,
98
+ type: Function
99
+ },
100
+ },
101
+ setup(props) {
102
+
103
+ const Table = new FlameTable(props.model as any, props.opts);
104
+
105
+ const FlameTableModal = ref<InstanceType<typeof ModalVue>>();
106
+
107
+ const ColumnNames = Object.keys(Table.model as any);
108
+
109
+ /**
110
+ * Добавление новой записи
111
+ */
112
+ const add = () => {
113
+ Table.mode = 'add';
114
+
115
+ // Установим все поля в пустые значения
116
+ ColumnNames.forEach(key => {
117
+ Table.columns[key].Popup.model = "";
118
+ })
119
+
120
+ FlameTableModal.value?.show();
121
+ }
122
+
123
+ /**
124
+ * Редактирование записи
125
+ */
126
+ const edit = async (row: any) => {
127
+ Table.mode = 'edit';
128
+
129
+ await Table.opts.Popup.load(row, Table);
130
+
131
+ // Установим все поля в фактические значения из таблицы
132
+ ColumnNames.forEach(key => {
133
+ Table.columns[key].Popup.model = row[key];
134
+ })
135
+
136
+ Table.OpenedRow = row;
137
+
138
+ FlameTableModal.value?.show();
139
+ }
140
+
141
+ const SaveTable = async () => {
142
+
143
+ try {
144
+ if (Table.mode === 'add') {
145
+ await Table.add()
146
+ }
147
+ else {
148
+ await Table.save()
149
+ }
150
+ }
151
+ catch (Ex) { return; }
152
+
153
+ FlameTableModal.value?.close();
154
+
155
+ Table.OpenedRow = null;
156
+
157
+ }
158
+
159
+ const deleteRow = async (row: any) => {
160
+ if (window.confirm("Удалить запись " + row + "?")) {
161
+ Table.remove(row);
162
+ }
163
+ }
164
+
165
+ const getSelector = (arr: any, selectorKey: any, selectorVal: any) => {
166
+ if (Array.isArray(arr)) return [selectorVal, selectorVal];
167
+ // TODO: тут загрузка объекта должна быть
168
+ if (typeof arr === 'object' && arr !== null) return [selectorKey, selectorVal]
169
+ return [selectorKey, selectorVal]
170
+ }
171
+
172
+ return {
173
+ Table,
174
+ FlameTableModal,
175
+ ColumnNames,
176
+ add,
177
+ edit,
178
+ SaveTable,
179
+ deleteRow,
180
+ getSelector
181
+ }
182
+
183
+ },
184
+
185
+ })
186
+
187
+ </script>
188
+
189
+ <style scoped lang="scss"></style>
@@ -0,0 +1,32 @@
1
+ <template lang="pug">
2
+ .flex
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ 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"
7
+
8
+ // Иконки
9
+ import { XCircleIcon } from '@icons/24/solid'
10
+ import { TableFilter } from './TableOpts.js';
11
+
12
+ // Глобальное хранилище и роуты
13
+ const store = storeFile(), router = useRouter(), route = useRoute();
14
+
15
+ // Локальное состояние компонента
16
+ const state = reactive({
17
+ data: {}
18
+ })
19
+
20
+ // Входящие данные компонента
21
+ const props = defineProps<{
22
+ filters: Array<TableFilter>
23
+ }>()
24
+
25
+ // Примонтировано
26
+ onMounted(async () => {
27
+
28
+ })
29
+
30
+ </script>
31
+
32
+ <style scoped lang="scss"></style>
@@ -0,0 +1,91 @@
1
+ import { Column, IColumn } from './Columns';
2
+
3
+ import merge from 'lodash.merge'
4
+ import FlameTable from './FlameTable';
5
+
6
+ export default class TableOpts {
7
+
8
+ /**
9
+ * Колонки
10
+ */
11
+ public columnsOpts: { [key: string]: IColumn } = {};
12
+
13
+ public set(ColumnName: string, mergingOpts: IColumn) {
14
+ // TODO: ЕСТЬ ОШИБКА???
15
+ if (this.columnsOpts[ColumnName] === undefined) this.columnsOpts[ColumnName] = new Column as any
16
+ merge(this.columnsOpts[ColumnName], mergingOpts)
17
+ }
18
+
19
+ /**
20
+ * Удалить одну колонку или сразу несколько
21
+ * @param ColumnName Полностью удалить строку
22
+ */
23
+ public delete(ColumnName: string | Array<string>) {
24
+
25
+ let arr: Array<string> = [];
26
+ if (typeof ColumnName === 'string') arr = [ColumnName as string];
27
+ else arr = ColumnName as Array<string>;
28
+
29
+ for (const key in arr) {
30
+ // TODO: ????? as any
31
+ if (this.columnsOpts[arr[key]] === undefined) this.columnsOpts[arr[key]] = new Column as any
32
+ merge(this.columnsOpts[arr[key]], { Table: { isShow: false }, Popup: { isShow: false } })
33
+ }
34
+
35
+ }
36
+
37
+ public Add = {
38
+ can: true,
39
+ buttonTitle: "Добавить",
40
+ func: () => {
41
+ }
42
+ };
43
+ public Remove = {
44
+ can: true,
45
+ func: () => {
46
+ }
47
+ };
48
+ public Edit = {
49
+ can: true,
50
+ func: () => {
51
+ }
52
+ };
53
+
54
+ public Popup = {
55
+ /**
56
+ * Функция при загрузке попапа
57
+ */
58
+ load: async (row: any, table: typeof FlameTable.prototype) => {
59
+ },
60
+
61
+ /**
62
+ * Выполняет код перед сохранением записи, и ждёт true для продолжения
63
+ * @param row
64
+ * @param table
65
+ * @returns
66
+ */
67
+ beforeEdit: async (row: any, table: typeof FlameTable.prototype): Promise<boolean> => {
68
+ return true;
69
+ },
70
+ beforeAdd: async (row: any, table: typeof FlameTable.prototype): Promise<boolean> => {
71
+ return true;
72
+ },
73
+
74
+
75
+
76
+ }
77
+
78
+
79
+ /**
80
+ * Запрос на поиск записей
81
+ */
82
+ public find = () => {
83
+
84
+ }
85
+
86
+
87
+ }
88
+
89
+ export interface TableFilter {
90
+ type: "float" | "integer" | "string" | "text" | "date" | "selector"
91
+ }
@@ -0,0 +1 @@
1
+ <?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="67px" id="Layer_1" style="enable-background:new 0 0 67 67;" version="1.1" viewBox="0 0 67 67" width="67px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M29.765,50.32h6.744V33.998h4.499l0.596-5.624h-5.095 l0.007-2.816c0-1.466,0.14-2.253,2.244-2.253h2.812V17.68h-4.5c-5.405,0-7.307,2.729-7.307,7.317v3.377h-3.369v5.625h3.369V50.32z M34,64C17.432,64,4,50.568,4,34C4,17.431,17.432,4,34,4s30,13.431,30,30C64,50.568,50.568,64,34,64z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#3A589B;"/></svg>
@@ -0,0 +1 @@
1
+ <?xml version="1.0" ?><svg data-name="Layer 1" id="Layer_1" viewBox="0 0 508.33 508.36" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style>.cls-1{fill:none;}.cls-2{fill:#fff;fill-rule:evenodd;}.cls-3{clip-path:url(#clip-path);}.cls-4{fill:#fbbc05;}.cls-5{fill:#ea4335;}.cls-6{fill:#34a853;}.cls-7{fill:#4285f4;}</style><clipPath id="clip-path" transform="translate(-1.83 -1.82)"><path class="cls-1" d="M392.69,221.58H253.09v57.88h80.36c-7.49,36.77-38.82,57.88-80.36,57.88a88.53,88.53,0,1,1,0-177.05A86.61,86.61,0,0,1,308.25,180l43.58-43.58C325.28,113.3,291.23,99,253.09,99a149.82,149.82,0,0,0,0,299.63c74.91,0,143-54.48,143-149.82A124.29,124.29,0,0,0,392.69,221.58Z"/></clipPath></defs><title/><path class="cls-2" d="M485,29.72c11.45,15.13,16.61,40.21,19.15,70.7,3.36,45.5,4.71,100.1,6,156.52-1.67,57.47-2.35,115.43-6,156.52-2.88,31.54-9,52.66-19.22,65.47-13,12.79-37.94,23.59-73.5,26.15-43.25,3.69-96.61,3.65-155.48,5.1-65.44-1.12-109.82-.64-156.38-5.08-36.32-2.52-60.08-13.19-74.7-26.07-10.83-14.54-14-30-17-66.24-3.75-41.85-4.58-98.56-6-155.54C4,200.57,4.13,143.44,7.9,100.74,10.43,67.05,14.42,44.4,24.65,30,39,17.77,63.48,11.69,100,8.69c50-5.85,102.06-7,155.88-6.87,55.39.09,108.56,1.67,156,6.34,32,2.56,58.48,8.07,73.07,21.56Z" transform="translate(-1.83 -1.82)"/><g class="cls-3"><path class="cls-4" d="M89.66,337.34V160.29l115.77,88.53Z" transform="translate(-1.83 -1.82)"/></g><g class="cls-3"><path class="cls-5" d="M89.66,160.29l115.77,88.53,47.67-41.54,163.43-26.56V85.38H89.66Z" transform="translate(-1.83 -1.82)"/></g><g class="cls-3"><path class="cls-6" d="M89.66,337.34,294,180.72l53.8,6.81L416.53,85.38V412.25H89.66Z" transform="translate(-1.83 -1.82)"/></g><g class="cls-3"><path class="cls-7" d="M416.53,412.25,205.43,248.82l-27.24-20.43,238.34-68.1Z" transform="translate(-1.83 -1.82)"/></g></svg>
Binary file
@@ -0,0 +1,87 @@
1
+ <template lang="pug">
2
+
3
+ // УНИВЕРСАЛЬНОЕ МОДАЛЬНОЕ ОКНО
4
+ Позволяет вызывать себя через show() и close()
5
+
6
+ CustomModal.overall(v-model="isShow" :close="close" style="z-index: 999999;")
7
+
8
+ // Этот слот для замены самого окна
9
+ slot
10
+
11
+ .bg-white.rounded-xl
12
+
13
+ // Этот слот позволяет заменить всё сразу внутри окна
14
+ slot(name="inner")
15
+
16
+ // Заголовок
17
+ .flex.items-center.px-5.border-b.border-b-gray-300(v-if="title !== ''")
18
+ h1.text-3xl.py-5 {{ title }}
19
+ XCircleIcon.ml-24.w-6.opacity-25.cursor-pointer(@click="close")
20
+
21
+ // Тело
22
+ .my-3.px-5
23
+ slot(name="body")
24
+ .text-left {{ body === '' ? 'Укажите сообщение или используйте слот body' : '' }}
25
+
26
+ // Кнопки
27
+ slot(name="buttons")
28
+ .border-t.border-t-gray-300.mb-4.pt-3.pr-5.flex.items-end.flex-col
29
+ button.cursor-pointer.ml-5.rounded-full.bg-blue-700.px-4.py-1.text-white(@click="close") {{ closeButton === '' ? 'Закрыть' : closeButton }}
30
+
31
+ </template>
32
+
33
+ <script lang="ts">
34
+ import { ref, defineComponent } from '@vue/runtime-core'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST from "flamerest";
35
+
36
+ // Иконки
37
+ import { XCircleIcon } from '@icons/24/solid'
38
+
39
+ export default defineComponent({
40
+
41
+ components: { XCircleIcon },
42
+
43
+ props: {
44
+ title: {
45
+ type: String,
46
+ default: "",
47
+ required: false
48
+ },
49
+ body: {
50
+ type: String,
51
+ default: "",
52
+ required: false
53
+ },
54
+ closeButton: {
55
+ type: String,
56
+ default: "",
57
+ required: false
58
+ },
59
+ },
60
+
61
+ setup(props) {
62
+
63
+ const isShow = ref(false)
64
+
65
+ function show() {
66
+ isShow.value = true
67
+ }
68
+
69
+ function close() {
70
+ isShow.value = false
71
+ }
72
+
73
+ return {
74
+ isShow,
75
+ show,
76
+ close,
77
+ }
78
+ },
79
+
80
+ })
81
+
82
+
83
+ </script>
84
+
85
+ <style scoped lang="scss">
86
+ .overall {}
87
+ </style>
@@ -0,0 +1,4 @@
1
+ import Table from "./Table/Table.vue";
2
+
3
+
4
+ export default { Table };