flameresttable 1.0.4 → 1.0.6
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/.eslintrc +2 -1
- package/.vscode/settings.json +4 -1
- package/README.md +7 -33
- package/package.json +1 -1
- package/src/Table/Columns.ts +3 -3
- package/src/Table/FlameTable.ts +2 -7
- package/src/Table/Table.vue +174 -63
- package/src/Table/TableOpts.ts +6 -0
- package/src/pages/Home.vue +3 -5
package/.eslintrc
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"vue/multi-word-component-names": "off",
|
|
16
16
|
"vue/no-multiple-template-root": "off",
|
|
17
17
|
"vue/no-duplicate-attributes": "off",
|
|
18
|
-
"linebreak-style": "off"
|
|
18
|
+
"linebreak-style": "off",
|
|
19
|
+
"vue/multiline-html-element-content-newline": "off"
|
|
19
20
|
},
|
|
20
21
|
"plugins": [
|
|
21
22
|
"@typescript-eslint"
|
package/.vscode/settings.json
CHANGED
package/README.md
CHANGED
|
@@ -1,39 +1,13 @@
|
|
|
1
|
-
# Vue 3 + Vite + Pinia
|
|
1
|
+
# Smart REST Table on Vue 3 + Vite + Pinia
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Обработчик после загрузки
|
|
4
|
+
opts.Popup.load = async (row: any) => {
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
}
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Vite
|
|
12
|
-
* Vue 3 [via composition api]
|
|
13
|
-
* Typescript
|
|
14
|
-
* Pinia as state manager
|
|
15
|
-
* Vue Router 4
|
|
16
|
-
* Tailwind as css framework
|
|
17
|
-
* Pug as html framework
|
|
18
|
-
* ESLint
|
|
19
|
-
* Capacitor as converter to Android and iOS apps
|
|
20
|
-
* VSCode ready with Volar
|
|
21
|
-
* [FlameREST](https://github.com/FlameArt/auto-rest-template-yii2) attached
|
|
22
|
-
* Precreated Signup, Auth and Password Reset pages
|
|
23
|
-
|
|
24
|
-
# VSCode improvements
|
|
25
|
-
|
|
26
|
-
Pug-lexer in Volar doesn`t support all tailwind functions currently. So you need to apply fix https://github.com/pugjs/pug/pull/3373
|
|
27
|
-
|
|
28
|
-
OR just fix the `Volar` extension directly in `%USERNAME%/.vscode/extensions/vue.volar-VERSION/dist/node/server.js` that
|
|
29
|
-
|
|
30
|
-
(/^\.([_a-z0-9\-]*[_a-z][_a-z0-9\-]*)/i, 'class');
|
|
31
|
-
|
|
32
|
-
to that
|
|
33
|
-
|
|
34
|
-
(/^\.([_a-z0-9\-]*[_a-z]([_a-z0-9\-\[\]]|:(?! ))*)/i, 'class');
|
|
35
|
-
|
|
36
|
-
Compilation works fine without any fixes
|
|
8
|
+
// Обработчик перед сохранением
|
|
9
|
+
opts.Popup.beforeEdit = async (row: any, table: typeof FlameTable.prototype): Promise<boolean> => {
|
|
10
|
+
}
|
|
37
11
|
|
|
38
12
|
# Licenses
|
|
39
13
|
MIT
|
package/package.json
CHANGED
package/src/Table/Columns.ts
CHANGED
|
@@ -108,7 +108,7 @@ export class Column implements IColumn {
|
|
|
108
108
|
// string - обычная строка или селектор строк
|
|
109
109
|
// text - text area
|
|
110
110
|
// button - кнопка
|
|
111
|
-
popupType: 'string' as "string" | "text" | "button" | "date" | "selector",
|
|
111
|
+
popupType: 'string' as "string" | "text" | "button" | "date" | "selector" | "image" | "file",
|
|
112
112
|
|
|
113
113
|
// Текст ошибки добавления/обновления
|
|
114
114
|
errorMessage: '',
|
|
@@ -227,7 +227,7 @@ export interface IColumn {
|
|
|
227
227
|
Popup?: {
|
|
228
228
|
|
|
229
229
|
// Тут привязка к реальным данным
|
|
230
|
-
model?:
|
|
230
|
+
model?: any,
|
|
231
231
|
|
|
232
232
|
// Заголовок поля в попапе
|
|
233
233
|
title?: string,
|
|
@@ -259,7 +259,7 @@ export interface IColumn {
|
|
|
259
259
|
// string - обычная строка или селектор строк
|
|
260
260
|
// text - text area
|
|
261
261
|
// button - кнопка
|
|
262
|
-
popupType?: "string" | "text" | "button" | "date" | "selector",
|
|
262
|
+
popupType?: "string" | "text" | "button" | "date" | "selector" | "image" | "file",
|
|
263
263
|
|
|
264
264
|
// Текст ошибки добавления/обновления
|
|
265
265
|
errorMessage?: string,
|
package/src/Table/FlameTable.ts
CHANGED
|
@@ -48,11 +48,6 @@ export default class FlameTable<T> {
|
|
|
48
48
|
total: 0,
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
* Параметры прогрузки
|
|
53
|
-
*/
|
|
54
|
-
public LoadParams = {} as any;
|
|
55
|
-
|
|
56
51
|
/**
|
|
57
52
|
* Открытая в попапе строка
|
|
58
53
|
*/
|
|
@@ -124,9 +119,9 @@ export default class FlameTable<T> {
|
|
|
124
119
|
*/
|
|
125
120
|
public async update(SaveLoadParams: any = null) {
|
|
126
121
|
|
|
127
|
-
if (SaveLoadParams !== null) this.LoadParams = SaveLoadParams;
|
|
122
|
+
if (SaveLoadParams !== null) this.opts.LoadParams = SaveLoadParams;
|
|
128
123
|
|
|
129
|
-
const rows: Rows<T> = await (this.model as any).constructor.all(Object.assign(this.LoadParams, { page: this.Pager.page, perPage: this.Pager.perPage }));
|
|
124
|
+
const rows: Rows<T> = await (this.model as any).constructor.all(Object.assign(this.opts.LoadParams, { page: this.Pager.page, perPage: this.Pager.perPage }));
|
|
130
125
|
|
|
131
126
|
this.load(rows);
|
|
132
127
|
|
package/src/Table/Table.vue
CHANGED
|
@@ -1,66 +1,147 @@
|
|
|
1
|
-
<template
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mt-0">
|
|
3
|
+
|
|
4
|
+
<!-- "ДОБАВИТЬ" И ЧИСЛО ЗАПИСЕЙ-->
|
|
5
|
+
<div class="flex items-center justify-between">
|
|
6
|
+
<slot name="defaultButtons">
|
|
7
|
+
<div v-show="$props.opts?.Add.can"
|
|
8
|
+
class="flex cursor-pointer justify-center items-center text-white block 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"
|
|
9
|
+
@click="add()">
|
|
10
|
+
<div class="text-xl">
|
|
11
|
+
+
|
|
12
|
+
</div>
|
|
13
|
+
<div class="ml-2">
|
|
14
|
+
{{ $props.opts?.Add.buttonTitle }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</slot>
|
|
18
|
+
<slot name="otherButtons" />
|
|
19
|
+
|
|
20
|
+
<!-- ЧИСЛО ЗАПИСЕЙ -->
|
|
21
|
+
<div class="text-xs text-gray-400">
|
|
22
|
+
Показано: {{ Table.Pager.total > Table.Pager.perPage * Table.Pager.page ?
|
|
23
|
+
Table.Pager.perPage * Table.Pager.page : Table.Pager.total }} / {{ Table.Pager.total }} записей
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- ТАБЛИЦА-->
|
|
29
|
+
<div class="mt-2 table w-full">
|
|
30
|
+
|
|
31
|
+
<!-- ЗАГОЛОВКИ СТОЛБЦОВ-->
|
|
32
|
+
<div class="table-header-group">
|
|
33
|
+
<div v-for="column in Table.columns" v-show="column.Table.isShow" :key="'cheader_' + column"
|
|
34
|
+
class="table-cell border-r border-r-slate-100 px-2">
|
|
35
|
+
<span>{{ column.title !== '' ? column.title :
|
|
36
|
+
column.name }}</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div v-if="opts.Edit.can" class="table-cell border-r border-r-slate-100 px-2">
|
|
39
|
+
<button class="bg-green-600 invisible">
|
|
40
|
+
Изменить
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
<div v-if="opts.Remove.can" class="table-cell border-r border-r-slate-100 px-2">
|
|
44
|
+
<button class="bg-green-600 invisible">
|
|
45
|
+
Удалить
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<!-- СТРОКИ-->
|
|
51
|
+
<div class="table-row-group">
|
|
52
|
+
|
|
53
|
+
<div v-for="row in (Table.Rows.rows)" :key="'row_' + (row as any).id"
|
|
54
|
+
class="table-row cursor-pointer hover:bg-slate-100">
|
|
55
|
+
<div v-for="column in Table.columns" v-show="column.Table.isShow" :key="'tc_' + column"
|
|
56
|
+
class="table-cell border-r border-r-slate-100 px-2 last:border-r-0 last:pr-0"
|
|
57
|
+
@click="column.Table.click(row, column)">
|
|
58
|
+
<span>{{
|
|
59
|
+
column.Table.value(row, column) }}</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div v-if="opts.Edit.can" class="table-cell border-r border-r-slate-100 px-2 text-center">
|
|
62
|
+
<button class="px-2 py-1 bg-green-600 text-white my-1" @click="edit(row)">
|
|
63
|
+
Изменить
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
<div v-if="opts.Remove.can" class="table-cell border-r border-r-slate-100 px-2 text-center">
|
|
67
|
+
<button class="px-2 py-1 bg-gray-600 opacity-30 text-white my-1" @click="deleteRow(row)">
|
|
68
|
+
Удалить
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- ПОСТРАНИЧНАЯ РАЗБИВКА -->
|
|
78
|
+
<Paginator class="mt-3" :table="Table" />
|
|
79
|
+
|
|
80
|
+
<!-- МОДАЛКА ДОБАВЛЕНИЯ-->
|
|
81
|
+
<ModalVue ref="FlameTableModal">
|
|
82
|
+
<template #default>
|
|
83
|
+
<div class="bg-white rounded-xl" :class="'w-full desktop:w-[777px]'">
|
|
84
|
+
|
|
85
|
+
<!-- СЛОТ ЗАГОЛОВКА ПОПАПА -->
|
|
86
|
+
<slot name="header" />
|
|
87
|
+
|
|
88
|
+
<!-- КОЛОНКИ ТАБЛИЦЫ-->
|
|
89
|
+
<label v-for="column in ColumnNames" v-show="Table.columns[column].Popup.isShow" :key="'c' + column"
|
|
90
|
+
class="flex items-stretch justify-center w-full my-1 ml-2">
|
|
91
|
+
<div class="text-left px-2 py-1 bg-slate-100" style="width: 110px">
|
|
92
|
+
<div>{{ Table.columns[column].Popup.title !== '' ? Table.columns[column].Popup.title === '' :
|
|
93
|
+
Table.columns[column].title !== '' ? Table.columns[column].title : Table.columns[column].name }}</div>
|
|
94
|
+
<div class="text-xs text-slate-400">{{ Table.columns[column].Popup.desc }}</div>
|
|
95
|
+
</div>
|
|
96
|
+
<div
|
|
97
|
+
v-if="Table.columns[column].Popup.popupType === 'string' || Table.columns[column].Popup.popupType === 'text'"
|
|
98
|
+
class="flex-1 mr-5 w-full flex flex-col self-stretch">
|
|
99
|
+
<input v-model="Table.columns[column].Popup.model"
|
|
100
|
+
class="h-full self-stretch py-1 px-2 w-full outline-none border border-slate-100" type="text"
|
|
101
|
+
:disabled="!Table.columns[column].Popup.isEnabled"
|
|
102
|
+
:placeholder="Table.columns[column].Popup.placeholder !== '' ? Table.columns[column].Popup.placeholder : Table.columns[column].title !== '' ? Table.columns[column].title : Table.columns[column].name">
|
|
103
|
+
</div>
|
|
104
|
+
<div v-if="Table.columns[column].Popup.popupType === 'date'"
|
|
105
|
+
class="flex-1 mr-5 w-full flex flex-col self-stretch">
|
|
106
|
+
<Datepicker v-model="Table.columns[column].Popup.model" :auto-apply="true" />
|
|
107
|
+
</div>
|
|
108
|
+
<div v-if="Table.columns[column].Popup.popupType === 'selector'"
|
|
109
|
+
class="flex-1 mr-5 w-full flex flex-col self-stretch"><select v-model="Table.columns[column].Popup.model"
|
|
110
|
+
class="h-full self-stretch py-1 px-2 w-full outline-none border border-slate-100">
|
|
111
|
+
<option v-for="(selectorVal, selectorKey) in Table.columns[column].Popup.Selector.values"
|
|
112
|
+
:key="'sel_' + selectorKey"
|
|
113
|
+
:value="getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[1]">{{
|
|
114
|
+
getSelector(Table.columns[column].Popup.Selector.values, selectorKey, selectorVal)[0] }}</option>
|
|
115
|
+
</select></div>
|
|
116
|
+
<div v-if="Table.columns[column].Popup.popupType === 'image'"
|
|
117
|
+
class="flex-1 border border-slate-100 mr-5 w-full flex self-stretch">
|
|
118
|
+
<div class="flex-1"><img :src="fileGetData(Table.columns[column].Popup.model)"></div><!-- Загрузчик--><label
|
|
119
|
+
:for="'cfile_' + column"><svg class="my-auto cursor-pointer" width="24" height="24" viewbox="0 0 24 24"
|
|
120
|
+
fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
121
|
+
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
122
|
+
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"
|
|
123
|
+
fill="#0F172A" />
|
|
124
|
+
</svg><input class="hidden" :name="'cfile_' + column" type="file"
|
|
125
|
+
@change="fileUpdated(column, $event)"></label>
|
|
126
|
+
</div>
|
|
127
|
+
</label>
|
|
128
|
+
|
|
129
|
+
<!-- КНОПКИ СОХРАНЕНИЯ-->
|
|
130
|
+
<div class="flex w-full mt-3 mb-2 justify-between items-center">
|
|
131
|
+
<div v-show="$props.opts?.Add.can"
|
|
132
|
+
class="cursor-pointer ml-2 text-lg py-2 text-white block bg-gray-400 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"
|
|
133
|
+
@click="Table.OpenedRow = null; FlameTableModal?.close()">
|
|
134
|
+
Отмена
|
|
135
|
+
</div>
|
|
136
|
+
<div
|
|
137
|
+
class="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 hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 dark:focus:ring-blue-900"
|
|
138
|
+
@click="SaveTable()">
|
|
139
|
+
{{ Table.mode === 'add' ? 'Добавить' : 'Сохранить' }}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
144
|
+
</ModalVue>
|
|
64
145
|
</template>
|
|
65
146
|
|
|
66
147
|
<script lang="ts">
|
|
@@ -98,6 +179,7 @@ export default defineComponent({
|
|
|
98
179
|
type: Function
|
|
99
180
|
},
|
|
100
181
|
},
|
|
182
|
+
|
|
101
183
|
setup(props) {
|
|
102
184
|
|
|
103
185
|
const Table = new FlameTable(props.model as any, props.opts);
|
|
@@ -162,6 +244,23 @@ export default defineComponent({
|
|
|
162
244
|
}
|
|
163
245
|
}
|
|
164
246
|
|
|
247
|
+
const fileUpdated = async (column: any, ev: any) => {
|
|
248
|
+
Table.columns[column].Popup.model = ev;
|
|
249
|
+
const prepared = (await REST.prepare({ myParam: ev })).myParam;
|
|
250
|
+
Table.columns[column].Popup.model = prepared
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/*
|
|
254
|
+
const fileGetData = (model: any) => {
|
|
255
|
+
return computed(r => {
|
|
256
|
+
if (model instanceof Array && model.length > 0) {
|
|
257
|
+
if (typeof model[0].data === 'string') return model[0].data;
|
|
258
|
+
if (typeof model[0].file === 'string') return model[0].file;
|
|
259
|
+
}
|
|
260
|
+
return "";
|
|
261
|
+
})
|
|
262
|
+
}*/
|
|
263
|
+
|
|
165
264
|
const getSelector = (arr: any, selectorKey: any, selectorVal: any) => {
|
|
166
265
|
if (Array.isArray(arr)) return [selectorVal, selectorVal];
|
|
167
266
|
// TODO: тут загрузка объекта должна быть
|
|
@@ -186,10 +285,22 @@ export default defineComponent({
|
|
|
186
285
|
edit,
|
|
187
286
|
SaveTable,
|
|
188
287
|
deleteRow,
|
|
288
|
+
fileUpdated,
|
|
189
289
|
getSelector
|
|
190
290
|
}
|
|
191
291
|
|
|
192
292
|
},
|
|
293
|
+
methods: {
|
|
294
|
+
fileGetData(thisArr: any) {
|
|
295
|
+
|
|
296
|
+
if (thisArr instanceof Array && thisArr.length > 0) {
|
|
297
|
+
if (typeof thisArr[0].data === 'string') return thisArr[0].data;
|
|
298
|
+
if (typeof thisArr[0].file === 'string') return thisArr[0].file;
|
|
299
|
+
}
|
|
300
|
+
return "";
|
|
301
|
+
|
|
302
|
+
}
|
|
303
|
+
},
|
|
193
304
|
|
|
194
305
|
})
|
|
195
306
|
|
package/src/Table/TableOpts.ts
CHANGED
|
@@ -15,6 +15,12 @@ export default class TableOpts {
|
|
|
15
15
|
*/
|
|
16
16
|
public columnsOpts: { [key: string]: IColumn } = {};
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Параметры прогрузки
|
|
20
|
+
*/
|
|
21
|
+
public LoadParams = {} as any;
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
public set(ColumnName: string, mergingOpts: IColumn) {
|
|
19
25
|
// TODO: ЕСТЬ ОШИБКА???
|
|
20
26
|
if (this.columnsOpts[ColumnName] === undefined) this.columnsOpts[ColumnName] = new Column as any
|
package/src/pages/Home.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template lang="pug">
|
|
2
2
|
.flex.justify-center.h-screen.items-center.flex-col
|
|
3
|
-
.px-8.py-6.mt-4.text-left.bg-white.shadow-lg
|
|
3
|
+
.px-8.py-6.mt-4.text-left.bg-white.shadow-lg(class="desktop:w-feed-lg")
|
|
4
4
|
| Welcome!
|
|
5
5
|
|
|
6
6
|
</template>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { onMounted, reactive, defineProps } from '@vue/runtime-core'; import { storeFile } from "@/store"; import { useRoute, useRouter } from 'vue-router'; import REST from "flamerest"
|
|
12
12
|
|
|
13
13
|
// Иконки
|
|
14
|
-
import { XCircleIcon } from '@icons/solid'
|
|
14
|
+
import { XCircleIcon } from '@icons/24/solid'
|
|
15
15
|
|
|
16
16
|
const store = storeFile(), router = useRouter(), route = useRoute();
|
|
17
17
|
|
|
@@ -28,6 +28,4 @@ onMounted(() => {
|
|
|
28
28
|
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
|
-
<style scoped lang="scss">
|
|
32
|
-
|
|
33
|
-
</style>
|
|
31
|
+
<style scoped lang="scss"></style>
|