QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.
- Source
- Introdução
- Parte I - ASP.NET - Inicializando os Projetos
- Parte 2 - PostgreSQL
- Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente
- Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity
- Parte 5 - ASP.NET - Documentação Interativa com Swagger
- Parte 6 - ASP.NET - Regionalização
- Parte 7 - ASP.NET - Autenticação e Autorização
- Parte 8 - ASP.NET - CORS
- Parte 9 - Quasar - Criação e Configuração do Projeto
- Parte 10 - Quasar - Configurações e Customizações
- Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR
- Parte 12 - Quasar - Serviços
- Parte 13 - Quasar - Regionalização e Stores
- Parte 14 - Quasar - Consumindo a API
- Parte 15 - Quasar - Login
- Parte 16 - Quasar - Áreas Protegidas
- Parte 17 - Quasar - Registro
- Parte 18 - Docker - Maquina Virtual Linux
- Parte 19 - Docker - Registro e Build
- Parte 20 - Docker - Traefik e Publicação
- Demo Online
22 Componente de Login
Iremos utilizar o componente de Login, para demonstrar as diferenças entre um componente feito para um SPA
, e um feito para um SSR
, que demanda que os dados sejam hidratados.
E antes de continuamos, a escolha por dividir os componentes SFC (Single File Component) em múltiplos arquivos, é algo de cunho pessoal. você pode ler mais a respeito em Single File Components - What About Separation of Concerns?
.
Caso durante a criação do projeto, tenha optado pela não importação automática dos componentes (
quasar.config.js
>framework
>all
> true). Você precisará instalar a seguinteqautomate
(como descrito no capitulo 19).
22.1 - SPA
o nosso primeiro passo, será criar os layouts, por hora iremos usar um layout clean
para as atividades de autenticação (landspage
, login
, registro
, etc) e outro para as demais paginas.
Lembrando que esta estrutura é apenas um exemplo, por mais que seja aplicável a maioria dos projetos, não encarre ela como sendo uma bala de prata.
o primeiro componente que iremos fazer, é o layout clean.
Quasar.App/src/layout/clean/index.vue
<template>
<q-layout id="layout-clean" view="lHh Lpr lFf" class="bg-main">
<q-page-container>
<q-page class="row">
<div class="col flex flex-center relative-position layout-auth">
<img alt="Quasar logo" class="absolute-center" src="~assets/quasar-logo-full.svg">
</div>
<div class="col col-auto shadow-up-2 page-container relative-position bg-content">
<div class="page-form q-pa-xl absolute-center">
<router-view />
</div>
</div>
</q-page>
</q-page-container>
</q-layout>
</template>
<script src="./index.js"></script>
<style src="./index.sass" lang="sass"></style>
Quasar.App/src/layout/clean/index.js
export default {
name: 'CleanLayout'
}
Quasar.App/src/layout/clean/index.sass
#layout-clean
.page-container
width: 540px !important
.page-form
width: 100%
@media (max-width: $breakpoint-sm-max)
.page-container
width: 100% !important
note que estamos usando o id do layout no primeiro nível do arquivo index.sass
, isto é necessário para garantir que este estilo será aplicado apenas para este componente e os seus respectivos filhos.
Agora, vamos criar a pagina responsável pelo login em QPANC.App/src/pages/login
QPANC.App/src/pages/login/index.vue
<template>
<div id="page-login">
<h5 class="q-my-md">{{$t('login.title')}}</h5>
<q-separator></q-separator>
<q-form class="row q-col-gutter-sm">
<div class="col col-12">
<q-input v-model="userName" :label="$t('fields.userName')" :rules="validation.userName"></q-input>
</div>
<div class="col col-12">
<q-input type="password" v-model="password" :label="$t('fields.password')" :rules="validation.password"></q-input>
</div>
<div class="col col-5">
<q-btn class="full-width" flat color="primary" :label="$t('actions.forget')" @click="forget"></q-btn>
</div>
<div class="col col-12">
<q-btn class="full-width" color="positive" :label="$t('actions.login')" @click="forget"></q-btn>
</div>
</q-form>
</div>
</template>
<script src="./index.js"></script>
<style src="./index.sass" lang="sass"></style>
QPANC.App/src/pages/login/index.js
import validations from 'services/validations'
export default {
name: 'LoginPage',
data () {
const self = this
const validation = validations(self, {
userName: ['required', 'email'],
password: ['required']
})
return {
userName: '',
password: '',
validation
}
},
methods: {
forget () {
console.log('forget: not implemented yet')
},
login () {
this.validation.resetServer()
const isValid = await this.$refs.form.validate()
if (isValid) {
console.log('login: not implemented yet')
}
}
}
}
QPANC.App/src/pages/login/index.sass
#page-login
No exemplo acima, o arquivo index.sass não possui nenhum estilo, por tanto ele é dispensável, podendo ser excluído.
Agora, precisamos modificar as nossas rotas em QPANC.App/src/routes
QPANC.App/src/routes/areas/clean.js
export default function (context) {
return {
path: '',
component: () => import('layouts/clean/index.vue'),
children: [
{ name: 'login', path: 'login', component: () => import('pages/login/index.vue') },
{ name: 'register', path: 'register', component: () => import('pages/register/index.vue') }
]
}
}
QPANC.App/src/routes/routes.js
import clean from './areas/clean'
export default function (context) {
const routes = [{
path: '/',
component: {
render: h => h('router-view')
},
children: [
{
path: '/',
beforeEnter (to, from, next) {
next('/login')
}
},
clean(context)
]
}]
// Always leave this as last one
if (process.env.MODE !== 'ssr') {
routes.push({
path: '*',
component: () => import('pages/Error404.vue')
})
}
return routes
}
QPANC.App/src/routes/routes.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'
Vue.use(VueRouter)
export default function (context) {
context.router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes: routes(context),
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
})
return context.router
}
Originalmente, o routes.js
retornava as rotas de forma direta, porém precisamos encapsular esta logica dentro de um função, para que possamos passar o contexto. Através do contexto, nós teremos acesso ao objeto router
, store
e ssrContext
, o que nós será bastante útil durante a construção dos navigation guards
.
Uma nota quanto ao component: { render: h => h('router-view') }
, esta é a forma que temos para definir uma rota no vue-router
que será renderizada, porém sem especificar um componente, esta técnica é especialmente útil, quando a intenção é unicamente agrupar as rotas.
Outro aspecto, é que não possuirmos uma rota na raiz da aplicação, por isto é necessário declarar uma rota com beforeEnter
que redireciona a aplicação para o /login
. Por hora poderíamos utilizar o redirect
ou alias
, mas futuramente este redirecionamento será condicionado ao fato do usuário está logado ou não. Lembrando que esta roda não será renderizada, por tanto, não é necessário utilizar o component: { render: h => h('router-view') }
.
Agora, podemos acessar a aplicação:
22.2 - SSR
Tecnicamente, o Login do jeito que está, já está apto para uma aplicação SSR, uma vez que, ele não faz nenhuma requisição assincronia durante a sua montagem/criação.
Porém, caso o fizesse, seria necessário criar um modulo no vuex dedicado para esta pagina, e mover as propriedades reativas que estão no data para o state deste modulo.
E por fim, fazer o carregamento dos dados no state usando uma action, que seria chamada no preFetch
.
Mas mesmo sem precisar, iremos converter a pagina para usar uma store, até para que todas as paginas do sistema venham a ter a mesma estrutura. O primeiro passo, será ativar o recurso do preFetch
no quasar.config.js > preFetch
:
QPANC.App/quasar.config.js
module.exports = function (ctx) {
return {
preFetch: true
}
}
O segundo passo, é criar uma store dentro da pasta da pagina, ou seja QPANC.App/src/pages/login
QPANC.App/src/pages/login/store.js
export default {
namespaced: true,
state () {
return {
userName: '',
password: ''
}
},
mutations: {
userName (state, value) { state.userName = value },
password (state, value) { state.password = value }
},
actions: {
async initialize ({ state }, { route, next }) {
},
forget ({ state }) {
console.log('forget: not implemented yet')
},
login ({ state }) {
console.log('login: not implemented yet')
}
}
}
Então, precisamos registrar este modulo no index.js
, assim como chamar a action initialize
no preFetch.
QPANC.App/src/pages/login/index.js
import validations from 'services/validations'
import pageModule from './store'
const moduleName = 'page-login'
export default {
name: 'LoginPage',
preFetch ({ store, currentRoute, redirect }) {
store.registerModule(moduleName, pageModule)
return store.dispatch(`${moduleName}/initialize`, { route: currentRoute, next: redirect })
},
created () {
if (process.env.CLIENT) {
this.$store.registerModule(moduleName, pageModule, { preserveState: true })
}
},
destroyed () {
this.$store.unregisterModule(moduleName)
},
data () {
const self = this
const validation = validations(self, {
userName: ['required', 'email'],
password: ['required']
})
return {
validation
}
},
computed: {
userName: {
get () { return this.$store.state[moduleName].userName },
set (value) { this.$store.commit(`${moduleName}/userName`, value) }
},
password: {
get () { return this.$store.state[moduleName].password },
set (value) { this.$store.commit(`${moduleName}/password`, value) }
}
},
methods: {
forget () {
this.$store.dispatch(`${moduleName}/forget`)
},
login () {
this.$store.dispatch(`${moduleName}/login`)
}
}
}
Os hooks preFetch
, created
e o destroyed
são usados para gerenciar o ciclo de vida do modulo do vuex
, para que ele tenha um ciclo de vida semelhante ao da pagina.
A action initialize
está sendo chamada do preFetch, desta forma a pagina será renderizada apenas quando o preFetch
for concluído.
No computed
, estamos mapeando o state
e os mutations
, para que seja possível utilizar o two-way bind
(v-model
) com o state
do vuex
.
O methods
está servindo apenas de proxy para as actions
do modulo dedicado a esta pagina.
E por fim, no data temos apenas campos de controle, utilitários, semi-estáticos, etc. Como por exemplo, o rules
ou as definições das colunas de uma tabela.
Caso não deseje usar a extensão sugerida no próximo tópico, recomendo que dê uma olhada no plugin vuex-map-fields
.
22.3 - SSR usando @toby-mosque/utils
O primeiro passo, é instalar a extensão '@toby.mosque/utils'
quasar ext add '@toby.mosque/utils'
altere o jsconfig.json
> compilerOptions
> paths
para incluir o seguinte item:
QPANC.App/jsconfig.json
{
"compilerOptions": {
"paths": {
"@toby.mosque/utils": [
"node_modules/@toby.mosque/quasar-app-extension-utils/src/utils.js"
]
}
}
}
Então, altere o arquivo store.js
QPANC.App/src/pages/login/store.js
import { factory } from '@toby.mosque/utils'
class LoginPageModel {
constructor ({
userName = '',
password = ''
} = {}) {
this.userName = userName
this.password = password
}
}
const options = {
model: LoginPageModel
}
export default factory.store({
options,
actions: {
async initialize ({ state }, { route, next }) {
},
forget ({ state }) {
console.log('forget: not implemented yet')
},
login ({ state }) {
console.log('login: not implemented yet')
}
}
})
export { options, LoginPageModel }
Note que as propriedades do state
e os respectivos mutations
foram movidos para a classe LoginPageModel
. A factory factory.store
irá criar o state
e o mutations
usando a classe LoginPageModel
como referencia.
Ao usar a factory.store
, a action initialize
torna-se um requisito, deve ser declarada, mesmo que não faça muito (ou nada).
Note que, apesar do factory.store
montar alguns states
, mutations
, getters
e actions
, você poderá declarar os seus próprios states
, mutations
, getters
e actions
, pois a store
gerada pelo factory.store
, será o resultado da mesclagem entre ambos.
Agora, modifique o script index.js
QPANC.App/src/pages/login/index.js
import validations from 'services/validations'
import { factory } from '@toby.mosque/utils'
import store, { options } from './store'
const moduleName = 'page-login'
export default factory.page({
name: 'LoginPage',
options,
moduleName,
storeModule: store,
data () {
const self = this
const validation = validations(self, {
userName: ['required', 'email'],
password: ['required']
})
return {
validation
}
},
methods: {
forget () {
this.$store.dispatch(`${moduleName}/forget`)
},
login () {
this.validation.resetServer()
const isValid = await this.$refs.form.validate()
if (isValid) {
this.$store.dispatch(`${moduleName}/login`)
}
}
}
})
Note, que não é mais necessário gerenciar o ciclo de vida do modulo (preFetch
, created
e destroyed
), assim como invocar a action initialize
, pois o factory.page
irá faze-lo por você.
Outro ponto que já não é necessário, é o mapeamento do state
e dos mutations
no computed
, pois ele também é feito pelo factory.page
.
Note que, apesar da factory.page
gerá os hooks preFetch
, created
, destroyed
, computed
e methods
, você poderá definir os seus próprios preFetch
, created
, destroyed
, computed
e methods
, pois a page
gerada pelo factory.page
, será o resultado da mesclagem entre ambos
Apenas um detalhe, o options
, além do campo model
, possui os campos collections
e complexTypes
. Eles são utilizados para criar/gerenciar um state do tipo Array
(collections
) ou Object
(complexTypes
), neste caso, será gerado getters
e actions
para acessar e manipular estes dados.
Para mais detalhes, leia: Quasar - Utility Belt App Extension to speedup the development of SSR and offline first apps.
22.4 - Considerações
A partir deste monto, estarei utilizando o @toby.mosque/utils
para a construção de layouts
e pages
.
Um outro ponto importante a se citar, é que todos os componentes de layout
e page
devem ser utilizados no routes.js
, assim como, o routes.js
só deve declarar componentes provenientes da pasta layouts
ou pages
.
Isto se faz necessário, pois o hook preFetch
existe apenas para os componentes que compõem a rota, pois o preFetch
é construído sobre um navigation guard, desta forma, não podemos usar a factory.page
para os demais componentes, ou seja, aqueles que iremos criar na pasta components
e serão consumidos pelos layouts
e pages
23 Exemplos avançados com a extensão @toby-mosque/utils
Caso tenha decidido em não usar a
@toby-mosque/utils
, você pode até ignorar este capitulo.Porém estarei exibindo a
store
e apage
que são geradas pelafactory.store
e pelafactory.page
, então você poderá estudar a estrutura delas, para aplicar este conhecimento nas suas própriasstores
epages
23.1 - Coleções
O primeiro exemplo, é sobre a geração de uma store
que possua um Array
, neste caso, iremos configurar o options collections
.
models/item.js
export default class ItemModel {
constructor ({
id = 0,
fieldA = '',
fieldB = ''
} = {}) {
this.id = id,
this.fieldA = fieldA
this.fieldB = fieldB
}
}
sample/store.js
import { factory } from '@toby.mosque/utils'
import ItemModel from 'models/item'
class SampleModel {
constructor ({
collectionA = [],
collectionB = []
} = {}) {
this.collectionA = collectionA
this.collectionB = collectionB
}
}
const options = {
model: SampleModel,
collections: [
{ single: 'itemA', plural: 'collectionA', id: 'id', type: ItemModel },
{ single: 'itemB', plural: 'collectionB', id: 'id', type: ItemModel }
]
}
export default factory.store({
options,
actions: {
async initialize ({ state }, { route, next }) {
}
}
})
export { options, LoginPageModel }
então, a partir do options
, a factory.store
será capaz de criar a seguinte store
.:
sample/store_generated.js
import Vue from 'vue'
export default {
namespaced: true,
state () {
return {
collectionA = [],
collectionB = []
}
},
mutations: {
collectionA (state, value) { Vue.set(state, 'collectionA', value) },
collectionB (state, value) { Vue.set(state, 'collectionB', value) },
createItemA (state, item) { state.collectionA.push(item) },
updateItemA (state, { index, item }) { Vue.set(state.collectionA, index, item) },
deleteItemA (state, index) { Vue.delete(state.collectionA, index) },
createItemB (state, item) { state.collectionB.push(item) },
updateItemB (state, { index, item }) { Vue.set(state.collectionB, index, item) },
deleteItemB (state, index) { Vue.delete(state.collectionB, index) }
setFieldAOfAnItemA (state, { index, value }) { Vue.set(state.collectionA[index], 'fieldA', item) }
setFieldBOfAnItemA (state, { index, value }) { Vue.set(state.collectionA[index], 'fieldB', item) }
setFieldAOfAnItemB (state, { index, value }) { Vue.set(state.collectionB[index], 'fieldA', item) }
setFieldBOfAnItemB (state, { index, value }) { Vue.set(state.collectionB[index], 'fieldB', item) }
},
actions: {
async initialize ({ state }, { route, next }) {
},
saveOrUpdateItemA ({ commit, getters }, item) {
const index = getters.collectionAIndex.get(item.id)
if (index !== undefined) {
commit('updateItemA', { index, item })
} else {
commit('createItemA', item)
}
},
deleteItemA ({ commit, getters }, id) {
const index = getters.collectionAIndex.get(id)
if (index !== undefined) {
commit('deleteItemA', index)
}
},
saveOrUpdateItemB ({ commit, getters }, item) {
const index = getters.collectionBIndex.get(item.id)
if (index !== undefined) {
commit('updateItemB', { index, item })
} else {
commit('createItemB', item)
}
},
deleteItemB ({ commit, getters }, id) {
const index = getters.collectionBIndex.get(id)
if (index !== undefined) {
commit('deleteItemB', index)
}
},
setFieldAOfAnItemA ({ commit, getters }, { id, value }) {
const index = getters.collectionAIndex.get(id)
if (index !== undefined) {
commit('setFieldAOfAnItemA', { index, value })
}
},
setFieldBOfAnItemA ({ commit, getters }, { id, value }) {
const index = getters.collectionAIndex.get(id)
if (index !== undefined) {
commit('setFieldBOfAnItemA', { index, value })
}
},
setFieldAOfAnItemB ({ commit, getters }, { id, value }) {
const index = getters.collectionBIndex.get(id)
if (index !== undefined) {
commit('setFieldAOfAnItemB', { index, value })
}
},
setFieldBOfAnItemB ({ commit, getters }, { id, value }) {
const index = getters.collectionBIndex.get(id)
if (index !== undefined) {
commit('setFieldBOfAnItemB', { index, value })
}
}
},
getters: {
collectionAIndex (state) {
return state.collectionA.reduce((map, item, index) => {
map.set(item.id, index)
return map
}, new Map())
},
itemAById (state, getters) {
return function itemAById(id) {
const index = getters.collectionAIndex.get(id)
if (index !== undefined) {
return state.collectionA[index]
}
}
},
collectionBIndex (state) {
return state.collectionB.reduce((map, item, index) => {
map.set(item.id, index)
return map
}, new Map())
},
itemBById (state, getters) {
return function itemBById(id) {
const index = getters.collectionBIndex.get(id)
if (index !== undefined) {
return state.collectionB[index]
}
}
}
}
}
Note que, o prefiro das actions
pode ser configurado, o default
é saveOrUpdate
e delete
, mas por exemplo, você pode alterar para upsert
e remove
.
Agora vamos a page.:
sample/index.js
import ItemASection from 'components/item-a-section/index.vue'
import ItemBSection from 'components/item-b-section/index.vue'
import { factory } from '@toby.mosque/utils'
import store, { options } from './store'
const moduleName = 'page-sample'
export default factory.page({
name: 'SamplePage',
options,
moduleName,
storeModule: store,
data () {
return {
moduleName
}
},
components: {
'item-a-section': ItemASection,
'item-b-section': ItemBSection
}
})
A page resultante será a seguinte.:
sample/index_generated.js
import ItemASection from 'components/item-a-section/index.vue'
import ItemBSection from 'components/item-b-section/index.vue'
import store from './store'
const moduleName = 'page-sample'
export default {
name: 'SamplePage',
preFetch ({ store, currentRoute, redirect }) {
store.registerModule(moduleName, pageModule)
return store.dispatch(`${moduleName}/initialize`, { route: currentRoute, next: redirect })
},
created () {
if (process.env.CLIENT) {
this.$store.registerModule(moduleName, pageModule, { preserveState: true })
}
},
destroyed () {
this.$store.unregisterModule(moduleName)
},
data () {
return {
moduleName
}
},
components: {
'item-a-section': ItemASection,
'item-b-section': ItemBSection
},
computed: {
collectionA: {
get () { return this.$store.state[moduleName].collectionA},
set (value) { this.$store.commit(`${moduleName}/collectionA`, value) }
},
collectionB: {
get () { return this.$store.state[moduleName].collectionB },
set (value) { this.$store.commit(`${moduleName}/collectionB`, value) }
},
itemAById () {
return this.$store.getters[`${moduleName}/itemAById`]
},
itemBById () {
return this.$store.getters[`${moduleName}/itemBById`]
},
collectionAIndex () {
return this.$store.getters[`${moduleName}/collectionAIndex`]
},
collectionBIndex () {
return this.$store.getters[`${moduleName}/collectionBIndex`]
},
},
methods: {
saveOrUpdateItemA (item) {
return this.$store.dispatch(`${moduleName}/saveOrUpdateItemA`, item)
},
deleteItemA (id) {
return this.$store.dispatch(`${moduleName}/deleteItemA`, id)
},
saveOrUpdateItemB (item) {
return this.$store.dispatch(`${moduleName}/saveOrUpdateItemB`, item)
},
deleteItemB (id) {
return this.$store.dispatch(`${moduleName}/deleteItemB`, id)
}
}
}
E para ilustrar, um template.:
sample/index.vue
<template>
<div>
<q-card v-for="itemA in collectionA" :key="itemA.id">
<item-a-section :module="moduleName" :id="itemA.id"></item-a-section>
<q-card-actions>
<q-btn icon="delete" @click="deleteItemA(itemA.id)" />
</q-card-actions>
</q-card>
<q-card v-for="itemB in collectionB" :key="itemB.id">
<item-b-section :module="moduleName" :id="itemB.id"></item-b-section>
<q-card-actions>
<q-btn icon="delete" @click="deleteItemA(itemB.id)" />
</q-card-actions>
</q-card>
</div>
</template>
Antes que me pergunte, as actions
/mutations
com formato semelhante à setFieldBOfAnItemA
foram feitas para serem utilizadas em componentes, no exemplo acima, o item-a-section
e item-b-section
.
Aqui a implementação do item-a-section
:
components/item-a-section/index.js
import { store } from '@toby.mosque/utils'
import ItemModel from 'models/item'
const module = store.mapCollectionItemState('', { id: 'id', single: 'itemA', type: ItemModel })
export default {
name: 'ItemAComponent',
props: {
uid: String,
module: String
},
created () {
module.setModuleName(this.module)
},
computed: {
...module.computed
}
}
e por fim o template para este componente:
components/item-a-section/index.vue
<q-card-section>
<div class="row q-col-gutter-sm">
<q-input class="col col-12" v-model="fieldA" label="Field A" />
<q-input class="col col-12" v-model="fieldB" label="Field B" />
</div>
</q-card-section>
E para ilustrar, o mesmo componente, mas sem o uso do mapCollectionItemState
.
components/item-a-section/index_generated.js
export default {
name: 'ItemAComponent',
props: {
uid: String,
module: String
},
created () {
module.setModuleName(this.module)
},
computed: {
itemAById () {
return this.$store.getters[`${this.module}/itemAById`]
},
itemA () {
return this.itemAById(this.id)
},
fieldA: {
get () { return this.itemA.fieldA },
set (value) {
this.$store.dispatch(`${this.module}/setFieldAOfAnItemA`, {
id: this.id,
value
})
}
},
fieldA: {
get () { return this.itemA.fieldB },
set (value) {
this.$store.dispatch(`${this.module}/setFieldBOfAnItemA`, {
id: this.id,
value
})
}
}
}
}
23.2 Tipos Complexos
Ao utilizar o @toby-mosque/utils
, o ideal é utilizar objetos com apenas tipos concretos (String
, Number
, Date
) e Array
, porém as vezes precisamos utilizar tipos complexos.
sample/store.js
import { factory } from '@toby.mosque/utils'
import ItemModel from 'models/item'
class SampleModel {
constructor ({
itemA = new ItemModel(),
itemB = new ItemModel()
} = {}) {
this.itemA = itemA
this.itemB = itemB
}
}
const options = {
model: SampleModel,
complexTypes: [
{ name: 'itemA', type: ItemModel },
{ name: 'itemB', type: ItemModel }
]
}
export default factory.store({
options,
actions: {
async initialize ({ state }, { route, next }) {
}
}
})
export { options, LoginPageModel }
esta seria a store
gerada pela factory.store
:
sample/store.js
export default {
options,
state () {
return {
itemA: {
id: 0,
fieldA: '',
fieldB: ''
},
itemB: {
id: 0,
fieldA: '',
fieldB: ''
}
}
},
mutations: {
itemA (state, value) { Vue.set(state, 'itemA', value) },
itemB (state, value) { Vue.set(state, 'itemB', value) },
setIdOfItemA (state, value) { state.itemA.id = value },
setFieldAOfItemA (state, value) { state.itemA.fieldA = value },
setFieldBOfItemA (state, value) { state.itemA.fieldB = value },
setIdOfItemB (state, value) { state.itemB.id = value },
setFieldAOfItemB (state, value) { state.itemB.fieldA = value },
setFieldBOfItemB (state, value) { state.itemB.fieldB = value }
},
actions: {
async initialize ({ state }, { route, next }) {
}
}
}
export { options, LoginPageModel }
Agora, vejamos a page:
sample/index.js
import { factory } from '@toby.mosque/utils'
import store, { options } from './store'
const moduleName = 'page-sample'
export default factory.page({
name: 'SamplePage',
options,
moduleName,
storeModule: store
})
E um exemplo de template:
sample/index.vue
<template>
<div>
<q-card>
<q-card-section>
Item A
</q-card-section>
<q-separator />
<q-card-section>
<div class="row q-col-gutter-sm">
<q-input class="col col-12" v-model="fieldAOfItemA" label="Field A" />
<q-input class="col col-12" v-model="fieldBOfItemA" label="Field B" />
</div>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
Item B
</q-card-section>
<q-separator />
<q-card-section>
<div class="row q-col-gutter-sm">
<q-input class="col col-12" v-model="fieldAOfItemB" label="Field A" />
<q-input class="col col-12" v-model="fieldBOfItemB" label="Field B" />
</div>
</q-card-section>
</q-card>
</div>
</template>
e para fins de comparação, a mesma page, forem sem uso da factory.page
sample/index_generated.js
const moduleName = 'page-sample'
export default {
name: 'SamplePage',
preFetch ({ store, currentRoute, redirect }) {
store.registerModule(moduleName, pageModule)
return store.dispatch(`${moduleName}/initialize`, { route: currentRoute, next: redirect })
},
created () {
if (process.env.CLIENT) {
this.$store.registerModule(moduleName, pageModule, { preserveState: true })
}
},
destroyed () {
this.$store.unregisterModule(moduleName)
},
computed () {
itemA: {
get () { return this.$store.state[moduleName].itemA },
set (value) { this.$store.commit(`${moduleName}/itemA`, value) }
},
itemB: {
get () { return this.$store.state[moduleName].itemA },
set (value) { this.$store.commit(`${moduleName}/itemA`, value) }
},
fieldAOfItemA: {
get () { return this.itemA.fieldA },
set (value) { this.$store.commit(`${moduleName}/setFieldAOfItemA`, value) }
},
fieldBOfItemA: {
get () { return this.itemA.fieldB },
set (value) { this.$store.commit(`${moduleName}/setFieldBOfItemA`, value) }
},
fieldAOfItemB: {
get () { return this.itemB.fieldA },
set (value) { this.$store.commit(`${moduleName}/setFieldAOfItemB`, value) }
},
fieldBOfItemB: {
get () { return this.itemB.fieldB },
set (value) { this.$store.commit(`${moduleName}/setFieldBOfItemB`, value) }
}
}
})
Quanto a geração e manutenção de stores
e pages
usando o @toby-mosque/utils
, não há muito mais a ser dito.
Top comments (0)