@bildvitta/quasar-ui-asteroid 3.16.0-beta.1 → 3.16.0-beta.10
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 +2 -1
- package/src/components/actions/QasActions.vue +7 -0
- package/src/components/actions-menu/QasActionsMenu.vue +3 -2
- package/src/components/app-menu/private/PvAppMenuHelpChat.vue +4 -2
- package/src/components/btn-dropdown/QasBtnDropdown.vue +3 -3
- package/src/components/expansion-item/QasExpansionItem.vue +117 -12
- package/src/components/expansion-item/QasExpansionItem.yml +8 -0
- package/src/components/form-view/QasFormView.vue +3 -1
- package/src/components/form-view/QasFormView.yml +3 -0
- package/src/components/grid-generator/QasGridGenerator.vue +2 -10
- package/src/components/header-actions/QasHeaderActions.vue +17 -4
- package/src/components/header-actions/QasHeaderActions.yml +6 -0
- package/src/components/label/QasLabel.vue +2 -2
- package/src/components/list-view/QasListView.vue +3 -1
- package/src/components/list-view/QasListView.yml +3 -0
- package/src/components/single-view/QasSingleView.vue +132 -81
- package/src/components/single-view/QasSingleView.yml +8 -0
- package/src/components/table-generator/QasTableGenerator.vue +16 -2
- package/src/components/table-generator/QasTableGenerator.yml +5 -0
- package/src/components/toggle-visibility/QasToggleVisibility.vue +79 -0
- package/src/components/toggle-visibility/QasToggleVisibility.yml +30 -0
- package/src/composables/private/index.js +3 -0
- package/src/composables/private/use-generator.js +9 -3
- package/src/composables/private/use-toggle-visibility.js +48 -0
- package/src/composables/private/use-view.js +186 -0
- package/src/mixins/view.js +12 -1
- package/src/vue-plugin.js +6 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bildvitta/quasar-ui-asteroid",
|
|
3
3
|
"description": "Asteroid",
|
|
4
|
-
"version": "3.16.0-beta.
|
|
4
|
+
"version": "3.16.0-beta.10",
|
|
5
5
|
"author": "Bild & Vitta <systemteam@bild.com.br>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/asteroid.cjs.min.js",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"sass": "^1.62.1"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@bildvitta/composables": "^1.0.0-beta.7",
|
|
46
47
|
"@bildvitta/store-adapter": "^1.0.0",
|
|
47
48
|
"autonumeric": "^4.9.0",
|
|
48
49
|
"axios": "^1.4.0",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="q-mt-sm" :class="classes">
|
|
3
|
+
<div v-if="hasTertiarySlot" :class="columnClasses">
|
|
4
|
+
<slot name="tertiary" />
|
|
5
|
+
</div>
|
|
6
|
+
|
|
3
7
|
<div v-if="hasSecondarySlot" :class="columnClasses">
|
|
4
8
|
<slot name="secondary" />
|
|
5
9
|
</div>
|
|
@@ -52,6 +56,8 @@ const classes = computed(() => {
|
|
|
52
56
|
const isSmallOrFullWidth = screen.isSmall || props.useFullWidth
|
|
53
57
|
|
|
54
58
|
return [
|
|
59
|
+
'items-center',
|
|
60
|
+
|
|
55
61
|
// alinhamento
|
|
56
62
|
`justify-${props.align}`,
|
|
57
63
|
|
|
@@ -71,4 +77,5 @@ const columnClasses = computed(() => {
|
|
|
71
77
|
|
|
72
78
|
const hasPrimarySlot = computed(() => !!slots.primary)
|
|
73
79
|
const hasSecondarySlot = computed(() => !!slots.secondary)
|
|
80
|
+
const hasTertiarySlot = computed(() => !!slots.tertiary)
|
|
74
81
|
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<qas-btn-dropdown v-bind="btnDropdownProps">
|
|
4
4
|
<q-list data-cy="actions-menu-list">
|
|
5
5
|
<slot v-for="(item, key) in formattedList.dropdownList" :item="item" :name="key">
|
|
6
|
-
<q-item v-bind="getItemProps(item)" :key="key" clickable data-cy="actions-menu-list-item" @click="setClickHandler(item)">
|
|
6
|
+
<q-item v-bind="getItemProps(item)" :key="key" active-class="primary" clickable data-cy="actions-menu-list-item" @click="setClickHandler(item)">
|
|
7
7
|
<q-item-section avatar>
|
|
8
8
|
<q-icon :name="item.icon" />
|
|
9
9
|
</q-item-section>
|
|
@@ -240,10 +240,11 @@ const { showTooltip, tooltipLabels } = useTooltips({ formattedList, fullList, pr
|
|
|
240
240
|
|
|
241
241
|
// functions
|
|
242
242
|
function getItemProps (item) {
|
|
243
|
-
const { disable, props: itemProps } = item
|
|
243
|
+
const { disable, props: itemProps, to } = item
|
|
244
244
|
|
|
245
245
|
return {
|
|
246
246
|
disable,
|
|
247
|
+
to,
|
|
247
248
|
...itemProps
|
|
248
249
|
}
|
|
249
250
|
}
|
|
@@ -106,10 +106,12 @@ onUnmounted(() => {
|
|
|
106
106
|
* Define o estilo do iframe de acordo com a posição do "chatContent" dentro
|
|
107
107
|
* do QMenu.
|
|
108
108
|
*/
|
|
109
|
-
function setIframeStyle () {
|
|
109
|
+
async function setIframeStyle () {
|
|
110
|
+
await nextTick()
|
|
111
|
+
|
|
110
112
|
const iframe = getIframe()
|
|
111
113
|
|
|
112
|
-
if (!iframe) return
|
|
114
|
+
if (!iframe || !chatContent.value) return
|
|
113
115
|
|
|
114
116
|
const { bottom, left, top } = chatContent.value.getBoundingClientRect()
|
|
115
117
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<div class="qas-btn-dropdown" :class="classes.parent">
|
|
3
3
|
<div v-if="hasButtons" :class="classes.list">
|
|
4
4
|
<div v-for="(buttonProps, key, index) in props.buttonsPropsList" :key="key">
|
|
5
|
-
<div class="flex">
|
|
6
|
-
<qas-btn :disable="props.disable" v-bind="buttonProps" variant="tertiary" @click="onClick">
|
|
5
|
+
<div class="flex no-wrap">
|
|
6
|
+
<qas-btn :disable="props.disable" v-bind="buttonProps" no-wrap variant="tertiary" @click="onClick">
|
|
7
7
|
<q-menu v-if="hasMenuOnLeftSide" v-model="isMenuOpened" anchor="bottom right" auto-close self="top right" @update:model-value="onUpdateMenuValue">
|
|
8
8
|
<div :class="classes.menuContent">
|
|
9
9
|
<slot />
|
|
@@ -91,7 +91,7 @@ const classes = computed(() => {
|
|
|
91
91
|
},
|
|
92
92
|
|
|
93
93
|
list: {
|
|
94
|
-
flex: !isSingleButton.value
|
|
94
|
+
'flex no-wrap': !isSingleButton.value
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
})
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="qas-expansion-item" :class="errorClasses">
|
|
3
|
-
<
|
|
4
|
-
<q-expansion-item header-class="text-bold q-mt-sm q-pa-none" :label="props.label">
|
|
2
|
+
<div ref="expansionItem" class="full-width qas-expansion-item" :class="errorClasses" v-bind="expansionProps.parent">
|
|
3
|
+
<component :is="component.is" class="qas-expansion-item__box">
|
|
4
|
+
<q-expansion-item header-class="text-bold q-mt-sm q-pa-none qas-expansion-item__header" :label="props.label" v-bind="expansionProps.item">
|
|
5
5
|
<template #header>
|
|
6
6
|
<slot name="header">
|
|
7
7
|
<div class="full-width">
|
|
8
8
|
<div class="items-center q-col-gutter-sm row">
|
|
9
9
|
<slot name="label">
|
|
10
|
-
<h5 class="col-auto text-h5
|
|
10
|
+
<h5 class="col-auto text-h5">
|
|
11
11
|
{{ props.label }}
|
|
12
12
|
</h5>
|
|
13
13
|
</slot>
|
|
@@ -18,17 +18,25 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
|
+
|
|
22
|
+
<div v-if="hasHeaderBottom" class="q-mt-sm">
|
|
23
|
+
<slot name="header-bottom" />
|
|
24
|
+
</div>
|
|
21
25
|
</div>
|
|
22
26
|
</slot>
|
|
23
27
|
</template>
|
|
24
28
|
|
|
25
|
-
<q-separator class="q-my-md" />
|
|
29
|
+
<q-separator v-if="hasHeaderSeparator" class="q-my-md" />
|
|
30
|
+
|
|
31
|
+
<div :class="contentClasses">
|
|
32
|
+
<slot name="content">
|
|
33
|
+
<qas-grid-generator v-if="hasGridGenerator" use-inline v-bind="gridGeneratorProps" />
|
|
34
|
+
</slot>
|
|
35
|
+
</div>
|
|
26
36
|
|
|
27
|
-
<
|
|
28
|
-
<qas-grid-generator v-if="hasGridGenerator" v-bind="gridGeneratorProps" use-inline />
|
|
29
|
-
</slot>
|
|
37
|
+
<q-separator v-if="hasBottomSeparator" class="q-mt-md" />
|
|
30
38
|
</q-expansion-item>
|
|
31
|
-
</
|
|
39
|
+
</component>
|
|
32
40
|
|
|
33
41
|
<div v-if="hasError" class="q-pt-sm qas-expansion-item__error-message text-caption text-negative">
|
|
34
42
|
{{ props.errorMessage }}
|
|
@@ -37,9 +45,14 @@
|
|
|
37
45
|
</template>
|
|
38
46
|
|
|
39
47
|
<script setup>
|
|
40
|
-
import
|
|
48
|
+
import QasBox from '../box/QasBox.vue'
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
import { computed, provide, inject, onMounted, ref, useAttrs } from 'vue'
|
|
51
|
+
|
|
52
|
+
defineOptions({
|
|
53
|
+
name: 'QasExpansionItem',
|
|
54
|
+
inheritAttrs: false
|
|
55
|
+
})
|
|
43
56
|
|
|
44
57
|
const props = defineProps({
|
|
45
58
|
badges: {
|
|
@@ -64,20 +77,112 @@ const props = defineProps({
|
|
|
64
77
|
gridGeneratorProps: {
|
|
65
78
|
type: Object,
|
|
66
79
|
default: () => ({})
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
useHeaderSeparator: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: undefined
|
|
67
85
|
}
|
|
68
86
|
})
|
|
69
87
|
|
|
88
|
+
const attrs = useAttrs()
|
|
89
|
+
|
|
90
|
+
provide('isExpansionItem', true)
|
|
91
|
+
|
|
92
|
+
// slots
|
|
93
|
+
const slots = defineSlots()
|
|
94
|
+
|
|
95
|
+
// refs
|
|
96
|
+
const expansionItem = ref(null)
|
|
97
|
+
const hasNextSibling = ref(false)
|
|
98
|
+
|
|
99
|
+
onMounted(setHasNextSibling)
|
|
100
|
+
|
|
101
|
+
// constants
|
|
102
|
+
const isNestedExpansionItem = inject('isExpansionItem', false)
|
|
103
|
+
const component = {
|
|
104
|
+
is: isNestedExpansionItem ? 'div' : QasBox
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// computed
|
|
70
108
|
const hasError = computed(() => props.error || !!props.errorMessage)
|
|
109
|
+
const hasGridGenerator = computed(() => !!Object.keys(props.gridGeneratorProps).length)
|
|
110
|
+
const hasBottomSeparator = computed(() => isNestedExpansionItem && hasNextSibling.value)
|
|
111
|
+
const hasHeaderBottom = computed(() => !!slots['header-bottom'])
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Verifica se o componente deve adicionar um separador no header.
|
|
115
|
+
*
|
|
116
|
+
* - Se a propriedade useHeaderSeparator for true, retorna separador.
|
|
117
|
+
* - Se a propriedade useHeaderSeparator for undefined, retorna separador apenas se não for um componente aninhado.
|
|
118
|
+
* - Se a propriedade useHeaderSeparator for false, não retorna separador.
|
|
119
|
+
*/
|
|
120
|
+
const hasHeaderSeparator = computed(() => {
|
|
121
|
+
return typeof props.useHeaderSeparator === 'undefined' ? !isNestedExpansionItem : props.useHeaderSeparator
|
|
122
|
+
})
|
|
71
123
|
|
|
72
124
|
const errorClasses = computed(() => ({ 'qas-expansion-item--error': hasError.value }))
|
|
73
125
|
|
|
74
|
-
const
|
|
126
|
+
const contentClasses = computed(() => {
|
|
127
|
+
return {
|
|
128
|
+
'q-mt-sm': isNestedExpansionItem,
|
|
129
|
+
'q-mt-md': !isNestedExpansionItem && !props.useHeaderSeparator
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const expansionProps = computed(() => {
|
|
134
|
+
const {
|
|
135
|
+
'onUpdate:modelValue': onUpdateModelValue,
|
|
136
|
+
onShow,
|
|
137
|
+
onBeforeShow,
|
|
138
|
+
onBeforeHide,
|
|
139
|
+
onAfterShow,
|
|
140
|
+
onAfterHide,
|
|
141
|
+
...propsPayload
|
|
142
|
+
} = attrs
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
parent: {
|
|
146
|
+
...propsPayload
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
item: {
|
|
150
|
+
onUpdateModelValue,
|
|
151
|
+
onShow,
|
|
152
|
+
onBeforeShow,
|
|
153
|
+
onBeforeHide,
|
|
154
|
+
onAfterShow,
|
|
155
|
+
onAfterHide
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// functions
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Caso o componente esteja dentro de um QasExpansionItem, verifica se existe um próximo irmão
|
|
164
|
+
* para adicionar um separador.
|
|
165
|
+
*/
|
|
166
|
+
function setHasNextSibling (value) {
|
|
167
|
+
if (!isNestedExpansionItem) return
|
|
168
|
+
|
|
169
|
+
const hasTextContentSibling = !!expansionItem.value.nextSibling?.textContent?.trim?.()
|
|
170
|
+
const hasElementSibling = !!expansionItem.value.nextElementSibling
|
|
171
|
+
|
|
172
|
+
hasNextSibling.value = hasElementSibling || hasTextContentSibling
|
|
173
|
+
}
|
|
75
174
|
</script>
|
|
76
175
|
|
|
77
176
|
<style lang="scss">
|
|
78
177
|
.qas-expansion-item {
|
|
79
178
|
$root: &;
|
|
80
179
|
|
|
180
|
+
// em alguns casos quando usado com grid, o espaçamento afetava o header, com z-index o problema é resolvido
|
|
181
|
+
&__header {
|
|
182
|
+
position: relative;
|
|
183
|
+
z-index: 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
81
186
|
&--error {
|
|
82
187
|
#{$root}__box {
|
|
83
188
|
border: 2px solid $negative;
|
|
@@ -24,10 +24,18 @@ props:
|
|
|
24
24
|
desc: Propriedades que serão repassadas para o QasGridGenerator.
|
|
25
25
|
type: Object
|
|
26
26
|
|
|
27
|
+
use-header-separator:
|
|
28
|
+
desc: Propriedade para forçar o QSeparator no header.
|
|
29
|
+
type: Boolean
|
|
30
|
+
default: undefined
|
|
31
|
+
|
|
27
32
|
slots:
|
|
28
33
|
header:
|
|
29
34
|
desc: 'Slot para substituir o conteúdo do header'
|
|
30
35
|
|
|
36
|
+
header-bottom:
|
|
37
|
+
desc: 'Slot para acessar o conteúdo que fica abaixo do header.'
|
|
38
|
+
|
|
31
39
|
label:
|
|
32
40
|
desc: 'Slot para substituir o conteúdo da label do header.'
|
|
33
41
|
|
|
@@ -70,7 +70,7 @@ const props = defineProps({
|
|
|
70
70
|
})
|
|
71
71
|
|
|
72
72
|
// composables
|
|
73
|
-
const { classes
|
|
73
|
+
const { classes, getFieldClass } = useGenerator({ props })
|
|
74
74
|
|
|
75
75
|
// computed
|
|
76
76
|
const hasResult = computed(() => Object.keys(props.result).length)
|
|
@@ -97,12 +97,6 @@ const headerClass = computed(() => {
|
|
|
97
97
|
]
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
const classes = computed(() => {
|
|
101
|
-
if (props.useInline) return 'row q-col-gutter-md'
|
|
102
|
-
|
|
103
|
-
return useGeneratorClasses.value
|
|
104
|
-
})
|
|
105
|
-
|
|
106
100
|
const fieldsByResult = ref({})
|
|
107
101
|
|
|
108
102
|
/**
|
|
@@ -163,9 +157,7 @@ function setFieldsByResult () {
|
|
|
163
157
|
}
|
|
164
158
|
|
|
165
159
|
function getContainerClass ({ key }) {
|
|
166
|
-
if (props.useInline)
|
|
167
|
-
return 'row justify-between col-12'
|
|
168
|
-
}
|
|
160
|
+
if (props.useInline) return 'row justify-between col-12'
|
|
169
161
|
|
|
170
162
|
return getFieldClass({ index: key, isGridGenerator: true })
|
|
171
163
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="justify-between no-wrap q-col-gutter-x-md
|
|
2
|
+
<div class="justify-between no-wrap q-col-gutter-x-md row text-body1 text-grey-8" :class="containerClass">
|
|
3
3
|
<div :class="leftClass">
|
|
4
4
|
<slot name="left">
|
|
5
5
|
{{ props.text }}
|
|
6
6
|
</slot>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
<div v-if="hasRightSide" class="
|
|
9
|
+
<div v-if="hasRightSide" class="justify-end row">
|
|
10
10
|
<slot name="right">
|
|
11
11
|
<qas-actions-menu v-if="hasDefaultActionsMenu" v-bind="props.actionsMenuProps" />
|
|
12
12
|
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
<script setup>
|
|
20
20
|
import { FlexAlign } from '../../enums/Align'
|
|
21
|
+
import { Spacing } from '../../enums/Spacing'
|
|
22
|
+
import { gutterValidator } from '../../helpers/private/gutter-validator'
|
|
21
23
|
|
|
22
24
|
import { computed, useSlots } from 'vue'
|
|
23
25
|
|
|
@@ -43,14 +45,25 @@ const props = defineProps({
|
|
|
43
45
|
text: {
|
|
44
46
|
type: String,
|
|
45
47
|
default: ''
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
spacing: {
|
|
51
|
+
default: Spacing.Xl,
|
|
52
|
+
type: String,
|
|
53
|
+
validator: gutterValidator
|
|
46
54
|
}
|
|
47
55
|
})
|
|
48
56
|
|
|
49
57
|
const slots = useSlots()
|
|
50
58
|
|
|
51
59
|
// computed
|
|
52
|
-
const containerClass = computed(() => `items-${props.alignColumns}`)
|
|
53
|
-
|
|
60
|
+
const containerClass = computed(() => `items-${props.alignColumns} q-mb-${props.spacing}`)
|
|
61
|
+
|
|
62
|
+
const leftClass = computed(() => {
|
|
63
|
+
return {
|
|
64
|
+
'col-12': !hasRightSide.value
|
|
65
|
+
}
|
|
66
|
+
})
|
|
54
67
|
|
|
55
68
|
const hasDefaultButton = computed(() => !!Object.keys(props.buttonProps).length)
|
|
56
69
|
const hasDefaultActionsMenu = computed(() => !!Object.keys(props.actionsMenuProps).length)
|
|
@@ -24,6 +24,12 @@ props:
|
|
|
24
24
|
desc: Descrição da seção a esquerda.
|
|
25
25
|
type: String
|
|
26
26
|
|
|
27
|
+
spacing:
|
|
28
|
+
desc: Espaçamento entre o componente e o conteúdo abaixo.
|
|
29
|
+
default: xl
|
|
30
|
+
type: String
|
|
31
|
+
examples: [none, xs, sm, md, lg, xl, '2xl', '3xl', '4xl', '5xl']
|
|
32
|
+
|
|
27
33
|
slots:
|
|
28
34
|
left:
|
|
29
35
|
desc: slot para acessar seção da esquerda (descrição).
|
|
@@ -1,124 +1,175 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="
|
|
3
|
-
<header v-if="
|
|
2
|
+
<div :class="componentClass">
|
|
3
|
+
<header v-if="hasHeaderSlot">
|
|
4
4
|
<slot name="header" />
|
|
5
5
|
</header>
|
|
6
6
|
|
|
7
|
-
<
|
|
7
|
+
<slot v-if="canShowFetchErrorSlot" name="fetch-error" />
|
|
8
|
+
|
|
9
|
+
<template v-else-if="hasResult">
|
|
8
10
|
<slot />
|
|
9
11
|
</template>
|
|
10
12
|
|
|
11
|
-
<
|
|
13
|
+
<div v-else-if="!viewState.fetching">
|
|
14
|
+
<qas-empty-result-text class="q-my-xl" />
|
|
15
|
+
</div>
|
|
12
16
|
|
|
13
|
-
<footer v-if="
|
|
17
|
+
<footer v-if="hasFooterSlot">
|
|
14
18
|
<slot name="footer" />
|
|
15
19
|
</footer>
|
|
16
20
|
|
|
17
|
-
<q-inner-loading :showing="
|
|
21
|
+
<q-inner-loading :showing="viewState.fetching">
|
|
18
22
|
<q-spinner color="grey" size="3em" />
|
|
19
23
|
</q-inner-loading>
|
|
20
24
|
</div>
|
|
21
25
|
</template>
|
|
22
26
|
|
|
23
|
-
<script>
|
|
24
|
-
import
|
|
27
|
+
<script setup>
|
|
28
|
+
import useView, { baseProps, baseEmits } from '../../composables/private/use-view'
|
|
25
29
|
|
|
26
|
-
import { markRaw } from 'vue'
|
|
27
|
-
import { getGetter, getAction } from '@bildvitta/store-adapter'
|
|
28
30
|
import debug from 'debug'
|
|
31
|
+
import { decamelize } from 'humps'
|
|
32
|
+
import { markRaw, computed, watch, inject } from 'vue'
|
|
33
|
+
import { useRoute } from 'vue-router'
|
|
29
34
|
|
|
30
35
|
const log = debug('asteroid-ui:qas-single-view')
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
name: 'QasSingleView',
|
|
34
|
-
|
|
35
|
-
mixins: [viewMixin],
|
|
37
|
+
defineOptions({ name: 'QasSingleView' })
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
default: '',
|
|
40
|
-
type: [Number, String]
|
|
41
|
-
},
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
...baseProps,
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
42
|
+
customId: {
|
|
43
|
+
default: '',
|
|
44
|
+
type: [Number, String]
|
|
47
45
|
},
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
result: {
|
|
48
|
+
default: () => ({}),
|
|
49
|
+
type: Object
|
|
50
|
+
}
|
|
51
|
+
})
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return !!this.resultModel
|
|
58
|
-
},
|
|
53
|
+
const emit = defineEmits([
|
|
54
|
+
...baseEmits,
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
'update:result',
|
|
57
|
+
'fetch-success',
|
|
58
|
+
'fetch-error'
|
|
59
|
+
])
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
return getGetter.call(this, { entity: this.entity, key: 'byId' })(this.id) || {}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
61
|
+
const slots = defineSlots()
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.mx_fetchHandler({ id: this.id, url: this.url }, this.fetchSingle)
|
|
73
|
-
}
|
|
74
|
-
},
|
|
63
|
+
// globals
|
|
64
|
+
const axios = inject('axios')
|
|
65
|
+
const qas = inject('qas')
|
|
75
66
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
},
|
|
67
|
+
// composables
|
|
68
|
+
const route = useRoute()
|
|
80
69
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
const {
|
|
71
|
+
// state
|
|
72
|
+
viewState,
|
|
73
|
+
|
|
74
|
+
// computed
|
|
75
|
+
componentClass,
|
|
76
|
+
hasHeaderSlot,
|
|
77
|
+
hasFooterSlot,
|
|
78
|
+
canShowFetchErrorSlot,
|
|
79
|
+
|
|
80
|
+
// functions
|
|
81
|
+
errorHandler,
|
|
82
|
+
fetchHandler,
|
|
83
|
+
setErrors,
|
|
84
|
+
setFields,
|
|
85
|
+
setMetadata,
|
|
86
|
+
setResult,
|
|
87
|
+
updateModels
|
|
88
|
+
} = useView({ emit, props, slots, mode: 'single' })
|
|
89
|
+
|
|
90
|
+
// Expose
|
|
91
|
+
defineExpose({ fetchSingle, fetchHandler })
|
|
92
|
+
|
|
93
|
+
// computed
|
|
94
|
+
const id = computed(() => props.customId || route.params.id)
|
|
95
|
+
|
|
96
|
+
const resultModel = computed(() => {
|
|
97
|
+
if (props.useStore) return qas.getGetter({ entity: props.entity, key: 'byId' })(id.value) || {}
|
|
98
|
+
|
|
99
|
+
return viewState.value.result || {}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const hasResult = computed(() => !!resultModel.value)
|
|
84
103
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
// watch
|
|
105
|
+
watch(() => route, (to, from) => {
|
|
106
|
+
if (to.name === from.name) {
|
|
107
|
+
fetchHandler({ id: id.value, url: props.url }, fetchSingle)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
watch(() => resultModel.value, value => emit('update:result', markRaw({ ...value })))
|
|
112
|
+
|
|
113
|
+
// created
|
|
114
|
+
fetchHandler({ id: id.value, url: props.url }, fetchSingle)
|
|
115
|
+
|
|
116
|
+
// functions
|
|
117
|
+
async function fetchSingle (externalPayload = {}) {
|
|
118
|
+
viewState.value.fetching = true
|
|
88
119
|
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
try {
|
|
121
|
+
const payload = { id: id.value, url: props.url, ...externalPayload }
|
|
91
122
|
|
|
92
|
-
|
|
93
|
-
entity: this.entity,
|
|
94
|
-
key: 'fetchSingle',
|
|
95
|
-
payload
|
|
96
|
-
})
|
|
123
|
+
const response = await getAction(payload)
|
|
97
124
|
|
|
98
|
-
|
|
125
|
+
const { errors, fields, metadata, result } = response.data
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
127
|
+
setErrors(errors)
|
|
128
|
+
setFields(fields)
|
|
129
|
+
setMetadata(metadata)
|
|
103
130
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
131
|
+
/**
|
|
132
|
+
* result só precisa ser adicionado ao estado se não estiver usando store,
|
|
133
|
+
* pois com a store é usado o getter.
|
|
134
|
+
*/
|
|
135
|
+
!props.useStore && setResult(result)
|
|
109
136
|
|
|
110
|
-
|
|
137
|
+
updateModels({
|
|
138
|
+
errors: viewState.value.errors,
|
|
139
|
+
fields: viewState.value.fields,
|
|
140
|
+
metadata: viewState.value.metadata,
|
|
111
141
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.mx_fetchError(error)
|
|
115
|
-
this.$emit('fetch-error', error)
|
|
142
|
+
...(!props.useStore && { result: viewState.value.result })
|
|
143
|
+
})
|
|
116
144
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
145
|
+
emit('fetch-success', response)
|
|
146
|
+
|
|
147
|
+
log(`[${props.entity}]:fetchSingle:success`, response)
|
|
148
|
+
} catch (error) {
|
|
149
|
+
errorHandler(error)
|
|
150
|
+
emit('fetch-error', error)
|
|
151
|
+
|
|
152
|
+
log(`[${props.entity}]:fetchSingle:error`, error)
|
|
153
|
+
} finally {
|
|
154
|
+
viewState.value.fetching = false
|
|
122
155
|
}
|
|
123
156
|
}
|
|
157
|
+
|
|
158
|
+
function getAction (payload) {
|
|
159
|
+
if (props.useStore) {
|
|
160
|
+
return qas.getAction({
|
|
161
|
+
entity: props.entity,
|
|
162
|
+
key: 'fetchSingle',
|
|
163
|
+
payload
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { id: unusedID, url: unusedURL, ...externalPayload } = payload
|
|
168
|
+
|
|
169
|
+
const decamelizedEntity = decamelize(props.entity, { separator: '-' })
|
|
170
|
+
|
|
171
|
+
const url = props.url || `${decamelizedEntity}/${id.value}`
|
|
172
|
+
|
|
173
|
+
return axios.get(url, { ...externalPayload })
|
|
174
|
+
}
|
|
124
175
|
</script>
|
|
@@ -62,10 +62,18 @@ props:
|
|
|
62
62
|
default: true
|
|
63
63
|
type: Boolean
|
|
64
64
|
|
|
65
|
+
use-route:
|
|
66
|
+
desc: Controla se o componente usará internamente uma store do vuex/pinia ou se vai ser utilizado o axios diretamente.
|
|
67
|
+
default: true
|
|
68
|
+
type: Boolean
|
|
69
|
+
|
|
65
70
|
slots:
|
|
66
71
|
default:
|
|
67
72
|
desc: 'Slot para ter o conteúdo principal (dentro do main).'
|
|
68
73
|
|
|
74
|
+
fetch-error:
|
|
75
|
+
desc: Slot disponibilizado em caso de fetchError.
|
|
76
|
+
|
|
69
77
|
footer:
|
|
70
78
|
desc: 'Slot para acessar o footer.'
|
|
71
79
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component :is="parentComponent.is" v-bind="parentComponent.props">
|
|
3
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" />
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</q-td>
|
|
16
16
|
</template>
|
|
17
17
|
</q-table>
|
|
18
|
-
</
|
|
18
|
+
</component>
|
|
19
19
|
</template>
|
|
20
20
|
|
|
21
21
|
<script>
|
|
@@ -57,6 +57,11 @@ export default {
|
|
|
57
57
|
type: String
|
|
58
58
|
},
|
|
59
59
|
|
|
60
|
+
useBox: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: true
|
|
63
|
+
},
|
|
64
|
+
|
|
60
65
|
useScrollOnGrab: {
|
|
61
66
|
type: Boolean,
|
|
62
67
|
default: true
|
|
@@ -217,6 +222,15 @@ export default {
|
|
|
217
222
|
|
|
218
223
|
hasScrollOnGrab () {
|
|
219
224
|
return !!Object.keys(this.scrollOnGrab).length
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
parentComponent () {
|
|
228
|
+
return {
|
|
229
|
+
is: this.useBox ? 'qas-box' : 'div',
|
|
230
|
+
props: {
|
|
231
|
+
class: this.useBox ? 'q-px-lg q-py-md' : ''
|
|
232
|
+
}
|
|
233
|
+
}
|
|
220
234
|
}
|
|
221
235
|
},
|
|
222
236
|
|
|
@@ -36,6 +36,11 @@ props:
|
|
|
36
36
|
type: Function
|
|
37
37
|
examples: ["(row) => ({ path: 'table-generator', params: { id: row.uuid } })"]
|
|
38
38
|
|
|
39
|
+
use-box:
|
|
40
|
+
desc: Controla se o componente vai usar QasBox ou div.
|
|
41
|
+
default: true
|
|
42
|
+
type: Boolean
|
|
43
|
+
|
|
39
44
|
use-external-link:
|
|
40
45
|
desc: Usado em conjunto com a prop "row-route-fn" quando há a necessidade da rota ser um link externo.
|
|
41
46
|
default: false
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="qas-toggle-visibility">
|
|
3
|
+
<div class="items-center no-wrap row" :style>
|
|
4
|
+
<div class="ellipsis qas-toggle-visibility__content">
|
|
5
|
+
<div
|
|
6
|
+
v-if="isVisible"
|
|
7
|
+
class="ellipsis full-width"
|
|
8
|
+
:title="props.text"
|
|
9
|
+
>
|
|
10
|
+
<slot>
|
|
11
|
+
{{ props.text }}
|
|
12
|
+
</slot>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<q-separator
|
|
16
|
+
v-else
|
|
17
|
+
size="4px"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<qas-btn
|
|
22
|
+
class="q-ml-sm qas-toggle-visibility__button"
|
|
23
|
+
:icon
|
|
24
|
+
@click.prevent.stop="toggleVisibility"
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
import { useToggleVisibility } from '../../composables/private'
|
|
32
|
+
|
|
33
|
+
import { uid } from 'quasar'
|
|
34
|
+
import { computed } from 'vue'
|
|
35
|
+
|
|
36
|
+
defineOptions({ name: 'QasToggleVisibility' })
|
|
37
|
+
|
|
38
|
+
const props = defineProps({
|
|
39
|
+
group: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: ''
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
text: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: ''
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
uuid: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: ''
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
width: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: '140px'
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const {
|
|
61
|
+
isVisible,
|
|
62
|
+
toggleVisibility
|
|
63
|
+
} = useToggleVisibility({ group: props.group, uuid: props.uuid || uid() })
|
|
64
|
+
|
|
65
|
+
const icon = computed(() => isVisible.value ? 'sym_r_visibility' : 'sym_r_visibility_off')
|
|
66
|
+
const style = computed(() => ({ width: props.width }))
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<style lang="scss">
|
|
70
|
+
.qas-toggle-visibility {
|
|
71
|
+
&__content {
|
|
72
|
+
flex-grow: 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&__button {
|
|
76
|
+
flex-grow: 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type: component
|
|
2
|
+
|
|
3
|
+
meta:
|
|
4
|
+
desc: Componente para ocultar e exibir conteúdo, utilizado para dados sensíveis.
|
|
5
|
+
|
|
6
|
+
props:
|
|
7
|
+
group:
|
|
8
|
+
desc: Propriedade para criar grupos de separação.
|
|
9
|
+
default: ''
|
|
10
|
+
type: String
|
|
11
|
+
examples: ["name", "document", "users"]
|
|
12
|
+
|
|
13
|
+
text:
|
|
14
|
+
desc: Texto a ser oculto/exibido.
|
|
15
|
+
default: ''
|
|
16
|
+
type: String
|
|
17
|
+
|
|
18
|
+
uuid:
|
|
19
|
+
desc: É criado um uuid random para cada componente, para que possa ser feito o controle interno, porém é possível sobrescrever por esta propriedade.
|
|
20
|
+
default: ''
|
|
21
|
+
type: String
|
|
22
|
+
|
|
23
|
+
width:
|
|
24
|
+
desc: Tamanho do conteúdo a ser oculto/exibido.
|
|
25
|
+
default: '140px'
|
|
26
|
+
type: String
|
|
27
|
+
|
|
28
|
+
slots:
|
|
29
|
+
default:
|
|
30
|
+
desc: Slot para inserir conteúdo a ser oculto/exibido.
|
|
@@ -21,7 +21,7 @@ export const baseProps = {
|
|
|
21
21
|
},
|
|
22
22
|
|
|
23
23
|
gutter: {
|
|
24
|
-
default:
|
|
24
|
+
default: undefined,
|
|
25
25
|
type: [String, Boolean],
|
|
26
26
|
validator: gutterValidator
|
|
27
27
|
}
|
|
@@ -46,13 +46,19 @@ export default function ({ props = {} }) {
|
|
|
46
46
|
const classes = computed(() => {
|
|
47
47
|
const classesList = ['row']
|
|
48
48
|
|
|
49
|
-
if (
|
|
50
|
-
classesList.push(`q-col-gutter-${
|
|
49
|
+
if (defaultGutter.value) {
|
|
50
|
+
classesList.push(`q-col-gutter-${defaultGutter.value}`)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
return classesList
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
+
const defaultGutter = computed(() => {
|
|
57
|
+
if (props.gutter) return props.gutter
|
|
58
|
+
|
|
59
|
+
return props.useInline ? Spacing.Md : Spacing.Lg
|
|
60
|
+
})
|
|
61
|
+
|
|
56
62
|
/**
|
|
57
63
|
* @function
|
|
58
64
|
* @param {Object} options
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ref, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
const visibleState = ref({})
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {{
|
|
7
|
+
* group?: string,
|
|
8
|
+
* uuid: string
|
|
9
|
+
* }}
|
|
10
|
+
*/
|
|
11
|
+
export default function useToggleVisibility ({ group, uuid }) {
|
|
12
|
+
group = group || 'default'
|
|
13
|
+
|
|
14
|
+
visibleState.value[group] = {
|
|
15
|
+
...visibleState.value[group],
|
|
16
|
+
[uuid]: false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isVisible = computed(() => visibleState.value[group]?.[uuid] || false)
|
|
20
|
+
|
|
21
|
+
function toggleVisibility () {
|
|
22
|
+
const item = visibleState.value[group]
|
|
23
|
+
const lastVisibleItemKey = Object.keys(item).find(key => !!item[key])
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* cenário onde esta visível e clicou no mesmo item
|
|
27
|
+
*/
|
|
28
|
+
if (visibleState.value[group][uuid]) {
|
|
29
|
+
visibleState.value[group][uuid] = false
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* cenário onde tem um item visível e clicou em outro item
|
|
35
|
+
*/
|
|
36
|
+
if (lastVisibleItemKey) {
|
|
37
|
+
visibleState.value[group][lastVisibleItemKey] = false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
visibleState.value[group][uuid] = true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
isVisible,
|
|
45
|
+
|
|
46
|
+
toggleVisibility
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { NotifyError } from '../../plugins'
|
|
2
|
+
import { camelizeFieldsName } from '../../helpers'
|
|
3
|
+
|
|
4
|
+
import { useView as useViewComposable } from '@bildvitta/composables'
|
|
5
|
+
import { ref, computed, watch, markRaw } from 'vue'
|
|
6
|
+
import { useRouter } from 'vue-router'
|
|
7
|
+
|
|
8
|
+
export const baseProps = {
|
|
9
|
+
beforeFetch: {
|
|
10
|
+
default: null,
|
|
11
|
+
type: Function
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
errors: {
|
|
15
|
+
default: () => ({}),
|
|
16
|
+
type: Object
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
entity: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: ''
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
fetching: {
|
|
25
|
+
type: Boolean
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
fields: {
|
|
29
|
+
default: () => ({}),
|
|
30
|
+
type: Object
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
metadata: {
|
|
34
|
+
default: () => ({}),
|
|
35
|
+
type: Object
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
url: {
|
|
39
|
+
default: '',
|
|
40
|
+
type: String
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
useBoundary: {
|
|
44
|
+
default: true,
|
|
45
|
+
type: Boolean
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
useStore: {
|
|
49
|
+
default: true,
|
|
50
|
+
type: Boolean
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const baseEmits = [
|
|
55
|
+
'update:fields',
|
|
56
|
+
'update:errors',
|
|
57
|
+
'update:metadata',
|
|
58
|
+
'update:fetching'
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {{
|
|
63
|
+
* mode: import('@bildvitta/composables').ViewModeTypes,
|
|
64
|
+
* props: baseProps,
|
|
65
|
+
* emit: (baseEmits: string) => void,
|
|
66
|
+
* slots: import('vue').Slots
|
|
67
|
+
* }} config
|
|
68
|
+
*/
|
|
69
|
+
export default function useView (config) {
|
|
70
|
+
const {
|
|
71
|
+
emit,
|
|
72
|
+
mode,
|
|
73
|
+
slots,
|
|
74
|
+
props
|
|
75
|
+
} = config
|
|
76
|
+
|
|
77
|
+
// composables
|
|
78
|
+
const router = useRouter()
|
|
79
|
+
const { viewState } = useViewComposable({ mode })
|
|
80
|
+
|
|
81
|
+
// refs
|
|
82
|
+
const cancelBeforeFetch = ref(false)
|
|
83
|
+
const hasFetchError = ref(false)
|
|
84
|
+
|
|
85
|
+
// constants
|
|
86
|
+
const fetchErrorMessage = 'Ops… Não conseguimos acessar as informações. Por favor, tente novamente em alguns minutos.'
|
|
87
|
+
|
|
88
|
+
// computed
|
|
89
|
+
const componentClass = computed(() => props.useBoundary && 'container spaced')
|
|
90
|
+
const hasFooterSlot = computed(() => !!slots.footer)
|
|
91
|
+
const hasHeaderSlot = computed(() => !!slots.header)
|
|
92
|
+
const hasFetchErrorSlot = computed(() => !!slots['fetch-error'])
|
|
93
|
+
const canShowFetchErrorSlot = computed(() => hasFetchErrorSlot.value && hasFetchError.value)
|
|
94
|
+
|
|
95
|
+
// watch
|
|
96
|
+
watch(() => viewState.value.fetching, value => emit('update:fetching', value))
|
|
97
|
+
|
|
98
|
+
// functions
|
|
99
|
+
function errorHandler (error) {
|
|
100
|
+
const { response } = error
|
|
101
|
+
|
|
102
|
+
const status = response?.status
|
|
103
|
+
|
|
104
|
+
const redirect = status >= 500
|
|
105
|
+
? 'ServerError'
|
|
106
|
+
: ({ 403: 'Forbidden', 404: 'NotFound' })[status]
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* caso exista um desses status será redirecionado sem aparecer o "notify"
|
|
110
|
+
*/
|
|
111
|
+
if (redirect) {
|
|
112
|
+
router.replace({ name: redirect })
|
|
113
|
+
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
hasFetchError.value = true
|
|
118
|
+
|
|
119
|
+
NotifyError(fetchErrorMessage)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function setErrors (errors = {}) {
|
|
123
|
+
viewState.value.errors = markRaw(errors)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setFields (fields = {}) {
|
|
127
|
+
const camelizedFields = camelizeFieldsName(fields)
|
|
128
|
+
|
|
129
|
+
viewState.value.fields = markRaw(camelizedFields)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function setMetadata (metadata = {}) {
|
|
133
|
+
viewState.value.metadata = markRaw(metadata)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function setResult (result = {}) {
|
|
137
|
+
viewState.value.result = result
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function updateModels (models) {
|
|
141
|
+
for (const key in models) {
|
|
142
|
+
if (!models[key]) continue
|
|
143
|
+
|
|
144
|
+
emit(`update:${key}`, models[key])
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function fetchHandler (payload, resolve) {
|
|
149
|
+
const hasBeforeFetch = typeof props.beforeFetch === 'function'
|
|
150
|
+
|
|
151
|
+
if (hasBeforeFetch && !cancelBeforeFetch.value) {
|
|
152
|
+
return props.beforeFetch({
|
|
153
|
+
payload,
|
|
154
|
+
resolve: payload => resolve(payload),
|
|
155
|
+
done: () => {
|
|
156
|
+
cancelBeforeFetch.value = true
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
resolve()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
// state
|
|
166
|
+
viewState,
|
|
167
|
+
|
|
168
|
+
// constants
|
|
169
|
+
fetchErrorMessage,
|
|
170
|
+
|
|
171
|
+
// computed
|
|
172
|
+
componentClass,
|
|
173
|
+
hasFooterSlot,
|
|
174
|
+
hasHeaderSlot,
|
|
175
|
+
canShowFetchErrorSlot,
|
|
176
|
+
|
|
177
|
+
// functions
|
|
178
|
+
errorHandler,
|
|
179
|
+
fetchHandler,
|
|
180
|
+
setErrors,
|
|
181
|
+
setFields,
|
|
182
|
+
setResult,
|
|
183
|
+
setMetadata,
|
|
184
|
+
updateModels
|
|
185
|
+
}
|
|
186
|
+
}
|
package/src/mixins/view.js
CHANGED
|
@@ -56,7 +56,8 @@ export default {
|
|
|
56
56
|
mx_fields: {},
|
|
57
57
|
mx_metadata: {},
|
|
58
58
|
mx_cancelBeforeFetch: false,
|
|
59
|
-
mx_isFetching: false
|
|
59
|
+
mx_isFetching: false,
|
|
60
|
+
mx_hasFetchError: false
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
|
|
@@ -79,8 +80,16 @@ export default {
|
|
|
79
80
|
return !!(this.$slots.header)
|
|
80
81
|
},
|
|
81
82
|
|
|
83
|
+
mx_hasFetchErrorSlot () {
|
|
84
|
+
return !!(this.$slots['fetch-error'])
|
|
85
|
+
},
|
|
86
|
+
|
|
82
87
|
mx_fetchErrorMessage () {
|
|
83
88
|
return 'Ops… Não conseguimos acessar as informações. Por favor, tente novamente em alguns minutos.'
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
mx_canShowFetchErrorSlot () {
|
|
92
|
+
return this.mx_hasFetchError && this.mx_hasFetchErrorSlot
|
|
84
93
|
}
|
|
85
94
|
},
|
|
86
95
|
|
|
@@ -101,6 +110,8 @@ export default {
|
|
|
101
110
|
return
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
this.mx_hasFetchError = true
|
|
114
|
+
|
|
104
115
|
this.$qas.error(this.mx_fetchErrorMessage)
|
|
105
116
|
},
|
|
106
117
|
|
package/src/vue-plugin.js
CHANGED
|
@@ -64,6 +64,7 @@ import QasTableGenerator from './components/table-generator/QasTableGenerator.vu
|
|
|
64
64
|
import QasTabsGenerator from './components/tabs-generator/QasTabsGenerator.vue'
|
|
65
65
|
import QasTextTruncate from './components/text-truncate/QasTextTruncate.vue'
|
|
66
66
|
import QasTimeline from './components/timeline/QasTimeline.vue'
|
|
67
|
+
import QasToggleVisibility from './components/toggle-visibility/QasToggleVisibility.vue'
|
|
67
68
|
import QasTransfer from './components/transfer/QasTransfer.vue'
|
|
68
69
|
import QasTreeGenerator from './components/tree-generator/QasTreeGenerator.vue'
|
|
69
70
|
import QasUploader from './components/uploader/QasUploader.vue'
|
|
@@ -72,7 +73,7 @@ import QasWhatsappLink from './components/whatsapp-link/QasWhatsappLink.vue'
|
|
|
72
73
|
|
|
73
74
|
import { Notify, Loading, Quasar, Dialog as QuasarDialog } from 'quasar'
|
|
74
75
|
|
|
75
|
-
import { getAction } from '@bildvitta/store-adapter'
|
|
76
|
+
import { getAction, getGetter } from '@bildvitta/store-adapter'
|
|
76
77
|
|
|
77
78
|
// Plugins
|
|
78
79
|
import {
|
|
@@ -157,6 +158,7 @@ async function install (app) {
|
|
|
157
158
|
app.component('QasTabsGenerator', QasTabsGenerator)
|
|
158
159
|
app.component('QasTextTruncate', QasTextTruncate)
|
|
159
160
|
app.component('QasTimeline', QasTimeline)
|
|
161
|
+
app.component('QasToggleVisibility', QasToggleVisibility)
|
|
160
162
|
app.component('QasTransfer', QasTransfer)
|
|
161
163
|
app.component('QasTreeGenerator', QasTreeGenerator)
|
|
162
164
|
app.component('QasUploader', QasUploader)
|
|
@@ -176,7 +178,8 @@ async function install (app) {
|
|
|
176
178
|
app.provide('qas', {
|
|
177
179
|
delete: params => Delete.call(app.config.globalProperties, params),
|
|
178
180
|
|
|
179
|
-
getAction: params => getAction.call(app.config.globalProperties, params)
|
|
181
|
+
getAction: params => getAction.call(app.config.globalProperties, params),
|
|
182
|
+
getGetter: params => getGetter.call(app.config.globalProperties, params)
|
|
180
183
|
})
|
|
181
184
|
|
|
182
185
|
app.directive(Test.name, Test)
|
|
@@ -251,6 +254,7 @@ export {
|
|
|
251
254
|
QasTabsGenerator,
|
|
252
255
|
QasTextTruncate,
|
|
253
256
|
QasTimeline,
|
|
257
|
+
QasToggleVisibility,
|
|
254
258
|
QasTransfer,
|
|
255
259
|
QasTreeGenerator,
|
|
256
260
|
QasUploader,
|