@bildvitta/quasar-ui-asteroid 3.8.0-beta.1 → 3.8.0-beta.2
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 +1 -1
- package/src/components/dialog/QasDialog.yml +1 -1
- package/src/components/field/QasField.vue +1 -1
- package/src/components/filters/QasFilters.vue +82 -48
- package/src/components/filters/QasFilters.yml +51 -9
- package/src/components/filters/private/PvFiltersButton.vue +89 -0
- package/src/components/form-view/QasFormView.vue +12 -0
- package/src/components/gallery-card/QasGalleryCard.vue +116 -0
- package/src/components/gallery-card/QasGalleryCard.yml +45 -0
- package/src/components/grid-generator/QasGridGenerator.vue +0 -5
- package/src/components/label/QasLabel.vue +19 -6
- package/src/components/nested-fields/QasNestedFields.vue +47 -30
- package/src/components/signature-uploader/QasSignatureUploader.vue +45 -57
- package/src/components/signature-uploader/QasSignatureUploader.yml +5 -9
- package/src/components/table-generator/QasTableGenerator.vue +39 -3
- package/src/components/table-generator/QasTableGenerator.yml +10 -0
- package/src/components/uploader/QasUploader.vue +330 -168
- package/src/components/uploader/QasUploader.yml +68 -12
- package/src/components/uploader/private/PvUploaderGalleryCard.vue +332 -0
- package/src/css/components/index.scss +1 -0
- package/src/css/components/scrollbar.scss +20 -0
- package/src/css/variables/index.scss +1 -0
- package/src/css/variables/scrollbar.scss +11 -0
- package/src/helpers/download-file.js +21 -0
- package/src/helpers/index.js +10 -9
- package/src/vue-plugin.js +3 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="text-
|
|
2
|
+
<div class="text-subtitle2" :class="classes">
|
|
3
3
|
<slot :label-with-suffix="labelWithSuffix">{{ labelWithSuffix }}</slot>
|
|
4
4
|
</div>
|
|
5
5
|
</template>
|
|
@@ -23,17 +23,30 @@ export default {
|
|
|
23
23
|
|
|
24
24
|
margin: {
|
|
25
25
|
default: 'sm',
|
|
26
|
-
type: String
|
|
26
|
+
type: String,
|
|
27
|
+
validator: value => {
|
|
28
|
+
const marginList = ['none', 'auto', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl']
|
|
29
|
+
|
|
30
|
+
return marginList.includes(value)
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
color: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: 'grey-9'
|
|
27
37
|
}
|
|
28
38
|
},
|
|
29
39
|
|
|
30
40
|
computed: {
|
|
31
|
-
labelClass () {
|
|
32
|
-
return `q-mb-${this.margin}`
|
|
33
|
-
},
|
|
34
|
-
|
|
35
41
|
labelWithSuffix () {
|
|
36
42
|
return addCounterSuffix(this.label, parseFloat(this.count))
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
classes () {
|
|
46
|
+
return [
|
|
47
|
+
`q-mb-${this.margin}`,
|
|
48
|
+
`text-${this.color}`
|
|
49
|
+
]
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
}
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
<div ref="inputContent">
|
|
8
8
|
<component :is="componentTag" v-bind="componentProps">
|
|
9
9
|
<div v-for="(row, index) in nested" :id="`row-${index}`" :key="`row-${index}`" class="full-width">
|
|
10
|
-
<div
|
|
10
|
+
<div :key="index" class="col-12 q-mt-md">
|
|
11
11
|
<div>
|
|
12
|
-
<
|
|
12
|
+
<header class="flex items-center q-py-md" :class="headerClasses">
|
|
13
13
|
<qas-label v-if="!useSingleLabel" :label="getRowLabel(index)" />
|
|
14
14
|
<qas-actions-menu v-if="hasBlockActions(row)" v-bind="actionsMenuProps" :list="getActionsList(index, row)" />
|
|
15
|
-
</
|
|
15
|
+
</header>
|
|
16
16
|
|
|
17
17
|
<div ref="formGenerator" class="col-12 justify-between q-col-gutter-x-md row">
|
|
18
18
|
<slot :errors="transformedErrors" :fields="children" :index="index" name="fields" :update-value="updateValuesFromInput">
|
|
@@ -70,7 +70,6 @@ import QasLabel from '../label/QasLabel.vue'
|
|
|
70
70
|
import { TransitionGroup } from 'vue'
|
|
71
71
|
|
|
72
72
|
import { constructObject } from '../../helpers'
|
|
73
|
-
import { extend } from 'quasar'
|
|
74
73
|
|
|
75
74
|
export default {
|
|
76
75
|
name: 'QasNestedFields',
|
|
@@ -242,7 +241,7 @@ export default {
|
|
|
242
241
|
|
|
243
242
|
data () {
|
|
244
243
|
return {
|
|
245
|
-
|
|
244
|
+
destroyKeyList: {},
|
|
246
245
|
hasDestroyAlways: true
|
|
247
246
|
}
|
|
248
247
|
},
|
|
@@ -290,18 +289,22 @@ export default {
|
|
|
290
289
|
|
|
291
290
|
showAddFirstInputButton () {
|
|
292
291
|
return this.useFirstInputButton && !this.nested.length
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
headerClasses () {
|
|
295
|
+
return this.useSingleLabel ? 'justify-end' : 'justify-between'
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
nested () {
|
|
299
|
+
return this.modelValue.filter(item => !item[this.destroyKey])
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
hasDestroyKeyList () {
|
|
303
|
+
return !!Object.keys(this.destroyKeyList).length
|
|
293
304
|
}
|
|
294
305
|
},
|
|
295
306
|
|
|
296
307
|
watch: {
|
|
297
|
-
modelValue: {
|
|
298
|
-
handler (value) {
|
|
299
|
-
this.nested = extend(true, [], value)
|
|
300
|
-
},
|
|
301
|
-
deep: true,
|
|
302
|
-
immediate: true
|
|
303
|
-
},
|
|
304
|
-
|
|
305
308
|
rowObject: {
|
|
306
309
|
handler () {
|
|
307
310
|
this.setDefaultNestedValue()
|
|
@@ -364,18 +367,7 @@ export default {
|
|
|
364
367
|
|
|
365
368
|
this.$qas.logger.group('QasNestedFields - add', [payload])
|
|
366
369
|
|
|
367
|
-
|
|
368
|
-
},
|
|
369
|
-
|
|
370
|
-
setFocus () {
|
|
371
|
-
const { formGenerator } = this.$refs
|
|
372
|
-
const firstElementToBeFocused = formGenerator.pop().querySelector('input, select, textarea')
|
|
373
|
-
|
|
374
|
-
return firstElementToBeFocused?.focus && firstElementToBeFocused.focus()
|
|
375
|
-
},
|
|
376
|
-
|
|
377
|
-
updateModelValue (value) {
|
|
378
|
-
return this.$emit('update:modelValue', value || this.nested)
|
|
370
|
+
this.updateModelValue()
|
|
379
371
|
},
|
|
380
372
|
|
|
381
373
|
/*
|
|
@@ -387,19 +379,35 @@ export default {
|
|
|
387
379
|
* ao invés de adicionar a flag [destroyKey]
|
|
388
380
|
*/
|
|
389
381
|
destroy (index, row) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
382
|
+
const [removedRow] = this.nested.splice(index, 1)
|
|
383
|
+
|
|
384
|
+
if (!this.useRemoveOnDestroy && row[this.identifierItemKey]) {
|
|
385
|
+
this.destroyKeyList[row[this.identifierItemKey]] = { row: removedRow, index }
|
|
386
|
+
}
|
|
393
387
|
|
|
394
388
|
this.$qas.logger.group('QasNestedFields - destroy', [{ index, row }])
|
|
395
389
|
|
|
396
|
-
|
|
390
|
+
this.updateModelValue()
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
updateModelValue () {
|
|
394
|
+
const nested = [...this.nested]
|
|
395
|
+
|
|
396
|
+
if (this.hasDestroyKeyList) {
|
|
397
|
+
for (const key in this.destroyKeyList) {
|
|
398
|
+
const { row, index } = this.destroyKeyList[key]
|
|
399
|
+
|
|
400
|
+
nested.splice(index, 0, { ...row, [this.destroyKey]: true })
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.$emit('update:modelValue', nested)
|
|
397
405
|
},
|
|
398
406
|
|
|
399
407
|
updateValuesFromInput (value, index) {
|
|
400
408
|
this.nested.splice(index, 1, value)
|
|
401
409
|
|
|
402
|
-
return this.updateModelValue(
|
|
410
|
+
return this.updateModelValue()
|
|
403
411
|
},
|
|
404
412
|
|
|
405
413
|
setDefaultNestedValue () {
|
|
@@ -419,6 +427,15 @@ export default {
|
|
|
419
427
|
})
|
|
420
428
|
},
|
|
421
429
|
|
|
430
|
+
async setFocus () {
|
|
431
|
+
await this.$nextTick()
|
|
432
|
+
|
|
433
|
+
const { formGenerator } = this.$refs
|
|
434
|
+
const firstElementToBeFocused = formGenerator.pop().querySelector('input, select, textarea')
|
|
435
|
+
|
|
436
|
+
return firstElementToBeFocused?.focus && firstElementToBeFocused.focus()
|
|
437
|
+
},
|
|
438
|
+
|
|
422
439
|
getRowLabel (rowKey) {
|
|
423
440
|
if (this.rowLabel) {
|
|
424
441
|
return this.useIndexLabel ? `${this.rowLabel} ${rowKey + 1}` : this.rowLabel
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<qas-uploader ref="uploader" v-model="model" :
|
|
4
|
-
<template #header="{ scope }">
|
|
5
|
-
<div class="cursor-pointer flex flex-center full-width justify-between no-border no-wrap q-gutter-xs text-white transparent" :class="headerClass" @click="openDialog">
|
|
6
|
-
<div class="col column items-start justify-center">
|
|
7
|
-
<div v-if="uploadLabel" class="q-uploader__title">{{ uploadLabel }}</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<qas-btn v-if="!readonly" color="white" icon="sym_r_add" :use-hover-on-white-color="false" variant="tertiary" @click="openDialog" />
|
|
11
|
-
|
|
12
|
-
<qas-btn ref="forceUpload" class="hidden" @click="upload(scope)" />
|
|
13
|
-
<qas-btn ref="buttonCleanFiles" class="hidden" @click="scope.removeUploadedFiles" />
|
|
14
|
-
</div>
|
|
15
|
-
</template>
|
|
16
|
-
</qas-uploader>
|
|
3
|
+
<qas-uploader ref="uploader" v-model="model" :add-button-fn="openDialog" :use-resize="false" v-bind="defaultUploaderProps" />
|
|
17
4
|
|
|
18
5
|
<qas-dialog v-model="isOpenedDialog" v-bind="defaultDialogProps">
|
|
19
6
|
<template #header>
|
|
@@ -30,7 +17,6 @@
|
|
|
30
17
|
</template>
|
|
31
18
|
|
|
32
19
|
<script>
|
|
33
|
-
import QasBtn from '../btn/QasBtn.vue'
|
|
34
20
|
import QasDialog from '../dialog/QasDialog.vue'
|
|
35
21
|
import QasUploader from '../uploader/QasUploader.vue'
|
|
36
22
|
import QasSignaturePad from '../signature-pad/QasSignaturePad.vue'
|
|
@@ -41,19 +27,20 @@ export default {
|
|
|
41
27
|
name: 'QasSignatureUploader',
|
|
42
28
|
|
|
43
29
|
components: {
|
|
44
|
-
QasBtn,
|
|
45
30
|
QasDialog,
|
|
46
31
|
QasUploader,
|
|
47
32
|
QasSignaturePad
|
|
48
33
|
},
|
|
49
34
|
|
|
35
|
+
inheritAttrs: false,
|
|
36
|
+
|
|
50
37
|
props: {
|
|
51
38
|
dialogProps: {
|
|
52
39
|
type: Object,
|
|
53
40
|
default: () => ({})
|
|
54
41
|
},
|
|
55
42
|
|
|
56
|
-
|
|
43
|
+
modelValue: {
|
|
57
44
|
default: '',
|
|
58
45
|
type: String
|
|
59
46
|
},
|
|
@@ -63,18 +50,14 @@ export default {
|
|
|
63
50
|
type: String
|
|
64
51
|
},
|
|
65
52
|
|
|
66
|
-
modelValue: {
|
|
67
|
-
default: '',
|
|
68
|
-
type: String
|
|
69
|
-
},
|
|
70
|
-
|
|
71
53
|
type: {
|
|
72
54
|
default: 'image/png',
|
|
73
55
|
type: String
|
|
74
56
|
},
|
|
75
57
|
|
|
76
|
-
|
|
77
|
-
type:
|
|
58
|
+
uploaderProps: {
|
|
59
|
+
type: Object,
|
|
60
|
+
default: () => ({})
|
|
78
61
|
}
|
|
79
62
|
},
|
|
80
63
|
|
|
@@ -82,27 +65,20 @@ export default {
|
|
|
82
65
|
|
|
83
66
|
data () {
|
|
84
67
|
return {
|
|
85
|
-
|
|
68
|
+
base64: '',
|
|
86
69
|
isEmpty: true,
|
|
87
|
-
|
|
70
|
+
isOpenedDialog: false
|
|
88
71
|
}
|
|
89
72
|
},
|
|
90
73
|
|
|
91
74
|
computed: {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
set (value) {
|
|
98
|
-
this.$emit('update:modelValue', value)
|
|
75
|
+
defaultUploaderProps () {
|
|
76
|
+
return {
|
|
77
|
+
addButtonLabel: 'Adicionar assinatura',
|
|
78
|
+
...this.uploaderProps
|
|
99
79
|
}
|
|
100
80
|
},
|
|
101
81
|
|
|
102
|
-
headerClass () {
|
|
103
|
-
return `q-pa-${this.readonly ? 'md' : 'sm'}`
|
|
104
|
-
},
|
|
105
|
-
|
|
106
82
|
defaultDialogProps () {
|
|
107
83
|
return {
|
|
108
84
|
maxWidth: '620px',
|
|
@@ -114,13 +90,14 @@ export default {
|
|
|
114
90
|
}
|
|
115
91
|
},
|
|
116
92
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
93
|
+
model: {
|
|
94
|
+
get () {
|
|
95
|
+
return this.modelValue
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
set (value) {
|
|
99
|
+
this.$emit('update:modelValue', value)
|
|
122
100
|
}
|
|
123
|
-
return sizes.true
|
|
124
101
|
},
|
|
125
102
|
|
|
126
103
|
signaturePadHeight () {
|
|
@@ -131,12 +108,30 @@ export default {
|
|
|
131
108
|
}
|
|
132
109
|
|
|
133
110
|
return sizes.true
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
signaturePadWidth () {
|
|
114
|
+
const sizes = {
|
|
115
|
+
[this.$qas.screen.isSmall]: { width: '100%' },
|
|
116
|
+
[this.$qas.screen.isMedium]: { width: '570px' },
|
|
117
|
+
[this.$qas.screen.isLarge]: { width: '350px' }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return sizes.true
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
uploaderScope () {
|
|
124
|
+
return this.$refs?.uploader?.uploader
|
|
134
125
|
}
|
|
135
126
|
},
|
|
136
127
|
|
|
137
128
|
methods: {
|
|
138
129
|
NotifyError,
|
|
139
130
|
|
|
131
|
+
closeSignature () {
|
|
132
|
+
this.isOpenedDialog = false
|
|
133
|
+
},
|
|
134
|
+
|
|
140
135
|
openDialog () {
|
|
141
136
|
if (this.readonly) return
|
|
142
137
|
|
|
@@ -144,31 +139,24 @@ export default {
|
|
|
144
139
|
},
|
|
145
140
|
|
|
146
141
|
getSignatureData () {
|
|
147
|
-
this
|
|
148
|
-
this.base64 = this.$refs.signaturePadModal.getSignatureData()
|
|
149
|
-
this.$refs.forceUpload.$el.click()
|
|
150
|
-
this.closeSignature()
|
|
151
|
-
},
|
|
142
|
+
this.uploaderScope.removeUploadedFiles()
|
|
152
143
|
|
|
153
|
-
|
|
154
|
-
this.isOpenedDialog = false
|
|
155
|
-
},
|
|
144
|
+
this.base64 = this.$refs.signaturePadModal.getSignatureData()
|
|
156
145
|
|
|
157
|
-
|
|
158
|
-
this
|
|
159
|
-
this.$emit('update:modelValue')
|
|
146
|
+
this.upload(this.uploaderScope)
|
|
147
|
+
this.closeSignature()
|
|
160
148
|
},
|
|
161
149
|
|
|
162
|
-
upload (
|
|
150
|
+
upload () {
|
|
163
151
|
try {
|
|
164
152
|
const fileName = this.signatureLabel.split(' ').join('-')
|
|
165
153
|
const extension = this.type.split('/').pop() || 'png'
|
|
166
154
|
const blob = base64ToBlob(this.base64)
|
|
167
155
|
const file = new File([blob], `${fileName}.${extension}`, { type: this.type })
|
|
168
156
|
|
|
169
|
-
|
|
157
|
+
this.uploaderScope.addFiles([file])
|
|
170
158
|
} catch {
|
|
171
|
-
NotifyError('
|
|
159
|
+
NotifyError('Falha ao carregar assinatura.')
|
|
172
160
|
}
|
|
173
161
|
}
|
|
174
162
|
}
|
|
@@ -13,11 +13,6 @@ props:
|
|
|
13
13
|
desc: Model do componente, retorna url da imagem upada, usando para v-model.
|
|
14
14
|
type: String
|
|
15
15
|
|
|
16
|
-
readonly:
|
|
17
|
-
desc: Controla se o componente vai ser apenas para visualização ou pode também fazer upload.
|
|
18
|
-
default: image/png
|
|
19
|
-
type: String
|
|
20
|
-
|
|
21
16
|
signature-label:
|
|
22
17
|
desc: Rótulo do componente de assinatura.
|
|
23
18
|
default: {}
|
|
@@ -27,10 +22,11 @@ props:
|
|
|
27
22
|
desc: Tipo da imagem gerada.
|
|
28
23
|
default: image/png
|
|
29
24
|
type: String
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
desc:
|
|
33
|
-
|
|
25
|
+
|
|
26
|
+
uploader-props:
|
|
27
|
+
desc: Propriedades repassadas para o QasUploader.
|
|
28
|
+
default: {}
|
|
29
|
+
type: Object
|
|
34
30
|
|
|
35
31
|
events:
|
|
36
32
|
'@update:model-value -> function(value)':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<qas-box class="q-px-lg q-py-md">
|
|
3
|
-
<q-table ref="table" class="bg-white qas-table-generator text-grey-8"
|
|
3
|
+
<q-table ref="table" class="bg-white qas-table-generator text-grey-8" v-bind="attributes">
|
|
4
4
|
<template v-for="(_, name) in slots" #[name]="context">
|
|
5
5
|
<slot :name="name" v-bind="context" />
|
|
6
6
|
</template>
|
|
@@ -65,6 +65,15 @@ export default {
|
|
|
65
65
|
|
|
66
66
|
useExternalLink: {
|
|
67
67
|
type: Boolean
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
useStickyHeader: {
|
|
71
|
+
type: Boolean
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
stickyHeaderTableHeight: {
|
|
75
|
+
default: '528px',
|
|
76
|
+
type: String
|
|
68
77
|
}
|
|
69
78
|
},
|
|
70
79
|
|
|
@@ -110,12 +119,14 @@ export default {
|
|
|
110
119
|
|
|
111
120
|
attributes () {
|
|
112
121
|
const attributes = {
|
|
122
|
+
class: this.tableClass,
|
|
113
123
|
columns: this.columnsByFields,
|
|
114
|
-
rows: this.resultsByFields,
|
|
115
124
|
flat: true,
|
|
116
125
|
hideBottom: true,
|
|
117
126
|
pagination: { rowsPerPage: 0 },
|
|
118
127
|
rowKey: this.rowKey,
|
|
128
|
+
rows: this.resultsByFields,
|
|
129
|
+
style: this.tableStyle,
|
|
119
130
|
|
|
120
131
|
// Eventos.
|
|
121
132
|
onRowClick: this.$attrs.onRowClick && this.onRowClick
|
|
@@ -196,7 +207,16 @@ export default {
|
|
|
196
207
|
},
|
|
197
208
|
|
|
198
209
|
tableClass () {
|
|
199
|
-
return
|
|
210
|
+
return {
|
|
211
|
+
'qas-table-generator--mobile': this.$qas.screen.isSmall,
|
|
212
|
+
'qas-table-generator--sticky-header': this.useStickyHeader
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
tableStyle () {
|
|
217
|
+
return {
|
|
218
|
+
maxHeight: this.useStickyHeader ? this.stickyHeaderTableHeight : 'initial'
|
|
219
|
+
}
|
|
200
220
|
},
|
|
201
221
|
|
|
202
222
|
hasScrollOnGrab () {
|
|
@@ -339,5 +359,21 @@ export default {
|
|
|
339
359
|
margin-left: 10px;
|
|
340
360
|
}
|
|
341
361
|
}
|
|
362
|
+
|
|
363
|
+
&--sticky-header {
|
|
364
|
+
.q-table thead tr {
|
|
365
|
+
th {
|
|
366
|
+
position: sticky;
|
|
367
|
+
z-index: 1;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
&:first-child {
|
|
371
|
+
th {
|
|
372
|
+
background-color: white;
|
|
373
|
+
top: 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
342
378
|
}
|
|
343
379
|
</style>
|
|
@@ -46,6 +46,16 @@ props:
|
|
|
46
46
|
default: false
|
|
47
47
|
type: Boolean
|
|
48
48
|
|
|
49
|
+
use-sticky-header:
|
|
50
|
+
desc: Usado para manter o header da tabela (thead) fixo.
|
|
51
|
+
default: false
|
|
52
|
+
type: Boolean
|
|
53
|
+
|
|
54
|
+
sticky-header-table-height:
|
|
55
|
+
desc: Usado para definir a altura máxima da tabela e exibir o scroll vertical quando a propriedade "use-sticky-header" é utilizada.
|
|
56
|
+
default: 528px
|
|
57
|
+
type: String
|
|
58
|
+
|
|
49
59
|
slots:
|
|
50
60
|
body:
|
|
51
61
|
desc: Acesso direto dentro do `tbody`.
|