Skip to content

Instantly share code, notes, and snippets.

@erikfig
Created July 15, 2019 15:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erikfig/a3a1f30b205e3a7a98167ca9f7b28527 to your computer and use it in GitHub Desktop.
Save erikfig/a3a1f30b205e3a7a98167ca9f7b28527 to your computer and use it in GitHub Desktop.
# Magic Crud
## Instalação
Copie os arquivos de acordo com a estrutura padrão do Quasar.
No futuro vou transformar em um pacote npm, mas eu chego lá.
## Como usar
Existem 2 vue components que são configurados para gerar o CRUD
- crud-list - página inicial de listagem
- crud-form - detalhes, edição e criação
### Rotas
As rotas são parte importante do processo, precisamos manter um padrão para que o crud possa permitir a navegação de forma correta
```
{
path: '/rota-base',
component: () => import('layouts/MyLayout.vue'),
children: [
{ path: '', component: () => import('pages/diretorio-dos-componentes-deste-moddulo/list.vue') },
{ path: 'novo', component: () => import('pages/diretorio-dos-componentes-deste-moddulo/create.vue') },
{ path: 'detalhes/:id', component: () => import('pages/diretorio-dos-componentes-deste-moddulo/details.vue') },
{ path: 'editar/:id', component: () => import('pages/diretorio-dos-componentes-deste-moddulo/edit.vue') },
],
},
```
### Listagem
Cria página de listagem de registros com paginação, botão de cadastro de novo registro e botões de ação por registro (ver, editar, remover e ativar/desativar).
Parâmetros:
- icon: String - Icone do título
- title: String - Título da página
- title-field: String - Define o campo que exibe o título do registro (exibido na ação de remover)
- label-btnNew: String - Personaliza o título do botão NOVO
- base-route: String - Qual a rota base para navegação e redirecionamentos (ver **Rotas**, acima)
- columns: Array - Colunas - [ver documentação do componente Table](https://quasar.dev/vue-components/table#Defining-the-columns) - também foi adicionado um novo recurso, para customizar o valor a ser exibido, ele se chama `filter`.
- remove: Function - Ação de remoção, executada após confirmar a remoção na caixa de diálogo (recebe o id do registro)
- visibility-callback: Function - Ação de ativação, desativação de registro, executada ao clicar no botão de ação do registro (recebe um objeto com os dados do registro)
- data: Array - Os dados vindos do servidor.
O campo title-field pode receber apenas o campo alvo, pode receber uma lista de opções (caso o valor não seja encontrado no primeiro campo, ele busca no seguinte e por ai vai), além disso, para definir um campo título em um relacionamento, basta usar `:`.
Exemplo:
```
<template>
<crud-list
title="Usuários PF ou PJ"
title-field="pf:nome_curto|pj:nome_fantasia"
icon="supervised_user_circle"
base-route="/rota-base"
:columns="columns"
:data="tableData"
:visibility-callback="visibilityCallback"
:remove="remove"/>
</template>
<script>
export default {
data() {
return {
columns: [
{
name: 'id',
label: 'id',
field: 'id',
sortable: true,
},
{
name: 'pf',
label: 'Razão Social/Nome Completo',
field: 'pf',
sortable: true,
filter(value, data) {
console.log(value, data);
if (value) {
return value.nome_completo;
}
return data.pf.razao_social;
},
},
{
name: 'pj',
label: 'Nome Fantasia/Nome Reduzido',
field: 'pj',
sortable: true,
filter(value, data) {
if (value) {
return value.nome_fantasia;
}
return data.pf.nome_curto;
},
},
],
};
},
computed: {
tableData() {
return this.$store.state.modulo.list.data;
},
},
methods: {
async remove(id) {
await this.$store.dispatch('modulo/remove', id);
this.$q.notify({
message: 'Removido com sucesso',
color: 'positive',
icon: 'sentiment_very_satisfied',
});
this.$store.dispatch('modulo/all');
},
async visibilityCallback(data) {
console.log(data);
await this.$store.dispatch('modulo/visibility', { id: data.id, active: data.active });
this.$store.dispatch('modulo/all');
},
},
mounted() {
this.$store.dispatch('modulo/all');
},
};
</script>
<style>
</style>
```
### Detalhes, edição e criação
Todas as 3 telas são criadas com base no component form, a grande diferença entre as páginas de edição e criação é que na primeira, passamos os dados do formulário, já a tela de detalhes recebe um parâmetro que desativa os campos do formulário, o submit e remove o botão de salvamento.
- icon: String - Icone do título
- title: String - Título da página
- form-title: String - Título no campo de formulário
- base-route: String - Qual a rota base para navegação e redirecionamentos (ver **Rotas**, acima)
- fields: Array - Array de campos do formulário (veja a seguir)
- v-model: Object - Os dados a serem exibidos no formulário
- submit: Function - O que acontece ao salvar o formulário e depois de todos os campos validados
- details: Boolean - Ativa a exibição de detalhes (falso, por padrão)
- v-model: Object - Dados para o formulário
O grande segredo aqui são os `fields`, eles definem quais campos seu formulário terá, como irá se parecer, e até as validações. Ele deve ser representado por um array de objetos, e cada objeto pode ter os seguintes parâmetros:
- field: String - Nome do campo (enviado para o Servidor ou quando requisitado)
- label: String - Etiqueta do campo
- type: String - Tipo do campo, podendo ser `select`, `state` (lista de estados), `bank` (lista de bancos), `data` (escolher data) e `text` (campo comum)
- options: Array - No caso do campo select, ele define as opções que podem ser selecionados, cada item recebe um `value` com o valor e o `label` com o texto a ser exibido.
- class: String - Classe para personalizar a exibição, o foco seria definir tamanhos de coluna para cada campo e assim definir tamanhos (largura) ou posicionar mais de um campo por 🇱inha, valor padrão é `col-12`.
- visibility: Function - Regra para exibição de um campo, recebe um objeto com os valores do model, deve retornar true ou false (retorna true por padrão)
- rules: Array - Regras de validação ([veja documentação oficial](https://quasar.dev/vue-components/input#Validation))
- mask: String - Mascara do campo ([veja documentação oficial](https://quasar.dev/vue-components/input#Mask))
- hint: String - Texto de ajuda para o campo
Exemplos:
```
// form_fields.js
import axios from 'axios';
import validations from '../../validations';
const uniqueCpf = v => axios.get(`/api/real-state-subsidiaries/check-cpf/${v}`);
const uniqueCnpj = v => axios.get(`/api/real-state-subsidiaries/check-cnpj/${v}`);
export default [
{
field: 'regime_juridico',
label: 'Regime jurídico',
type: 'select',
rules: [validations.required],
options: [
{
label: 'Pessoa Física',
value: 'F',
},
{
label: 'Pessoa Jurídica',
value: 'J',
},
],
class: 'col-6',
},
{
field: 'nome_completo',
label: 'Nome completo',
visibility: data => data.regime_juridico === 'F',
rules: [validations.required],
},
{
field: 'nome_curto',
label: 'Nome curto',
visibility: data => data.regime_juridico === 'F',
rules: [validations.required],
},
{
field: 'cpf',
label: 'CPF',
mask: '###.###.###-##',
visibility: data => data.regime_juridico === 'F',
rules: [validations.cpfCnpj, uniqueCpf],
},
{
field: 'identidade',
label: 'RG',
visibility: data => data.regime_juridico === 'F',
class: 'col-6',
},
{
field: 'orgao_emissor_identidade',
label: 'Orgão emissor',
visibility: data => data.regime_juridico === 'F',
class: 'col-3',
},
{
field: 'data_emissao_identidade',
label: 'Data de emissão',
type: 'date',
visibility: data => data.regime_juridico === 'F',
class: 'col-3',
},
{
field: 'razao_social',
label: 'Razão social',
visibility: data => data.regime_juridico === 'J',
rules: [validations.required],
},
{
field: 'nome_fantasia',
label: 'Nome fantasia',
visibility: data => data.regime_juridico === 'J',
rules: [validations.required],
},
{
field: 'cnpj',
label: 'CNPJ',
mask: '##.###.###/####-##',
visibility: data => data.regime_juridico === 'J',
rules: [validations.cpfCnpj, uniqueCnpj],
},
{
field: 'inscricao_estadual',
label: 'Inscrição estadual',
visibility: data => data.regime_juridico === 'J',
},
{
field: 'inscricao_municipal',
label: 'Inscrição municipal',
visibility: data => data.regime_juridico === 'J',
},
{
field: 'cep',
label: 'CEP',
mask: '########',
rules: [validations.cep],
},
{
field: 'logradouro',
label: 'Logradouro',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.required],
},
{
field: 'numero',
label: 'Número',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.required],
},
{
field: 'complemento',
label: 'Complemento',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
},
{
field: 'bairro',
label: 'Bairro',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.required],
},
{
field: 'municipio',
label: 'Município',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.required],
},
{
field: 'estado',
label: 'Estado',
type: 'state',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.required],
},
{
field: 'telefone1',
label: 'Telefone 1',
mask: '+## (##) #####-####',
hint: 'Com DDI, exemplo: +55 (11) 98888-8888 - DDI do Brasil: 55',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.required],
class: 'col-6',
},
{
field: 'telefone2',
label: 'Telefone 2',
mask: '+## (##) #####-####',
hint: 'Com DDI, exemplo: +55 (11) 98888-8888 - DDI do Brasil: 55',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
class: 'col-6',
},
{
field: 'email',
label: 'E-mail',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.email],
},
{
field: 'site',
label: 'Site',
visibility(data) {
return data.cep ? data.cep.length === 8 : false;
},
rules: [validations.url],
},
];
```
```
// details.vue
<template>
<crud-form
:details="true"
title="Usuários"
icon="supervised_user_circle"
base-route="/rota-base"
:fields="fields"
v-model="data"/>
</template>
<script>
import formFields from './form_fields';
export default {
data() {
return {
fields: formFields,
};
},
computed: {
data() {
return this.$store.state.modulo.first;
},
},
mounted() {
this.$store.dispatch('modulo/one', this.$route.params.id);
},
};
</script>
<style>
</style>
```
```
// edit.vue
<template>
<crud-form
formTitle="Editando Registro"
title="Registros"
icon="supervised_user_circle"
base-route="/rota-base"
:fields="fields"
:submit="submit"
v-model="tableData"/>
</template>
<script>
import formFields from './form_fields';
export default {
data() {
return {
fields: formFields,
};
},
computed: {
tableData() {
return this.$store.state.modulo.first;
},
},
methods: {
submit(data) {
this.$store.dispatch('modulo/update', { data, id: this.$route.params.id })
.then(() => {
this.$q.notify({
message: 'Salvo com sucesso',
color: 'positive',
icon: 'sentiment_very_satisfied',
});
this.$router.push('/rota-base');
})
.catch((error) => {
const { status } = error.response;
const message = `Os dados não foram salvos, verifique as informações! <em>Erro ${status}</em>`;
this.$q.notify({
message,
html: true,
color: 'negative',
icon: 'sentiment_very_dissatisfied',
});
});
},
},
mounted() {
this.$store.dispatch('modulo/one', this.$route.params.id);
},
};
</script>
<style>
</style>
```
```
<template>
<crud-form
formTitle="Cadastrando Registro"
title="Registros"
icon="supervised_user_circle"
base-route="/rota-base"
:fields="fields"
v-model="data"
:submit="submit"/>
</template>
<script>
import formFields from './form_fields';
export default {
data() {
return {
data: {},
fields: formFields,
};
},
methods: {
submit(data) {
this.$store.dispatch('modulo/create', data)
.then(() => {
this.$q.notify({
message: 'Salvo com sucesso',
color: 'positive',
icon: 'sentiment_very_satisfied',
});
this.$router.push('/rota-base');
})
.catch((error) => {
const { status } = error.response;
const message = `Os dados não foram salvos, verifique as informações! <em>Erro ${status}</em>`;
this.$q.notify({
message,
html: true,
color: 'negative',
icon: 'sentiment_very_dissatisfied',
});
});
},
},
};
</script>
<style>
</style>
```
### Considerações
Ainda está em desenvolvimento e está é a primeira documentação, espero facilitar o uso cada vez mais, qualquer consideração, por favor, me comunique.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment