Cenário de Implementação: Ao clicar em um campo de entrada, um teclado numérico personalizado é exibido. Após a digitação dos números e confirmação, o valor é exibido no campo. Se o campo já contiver um valor, este será pré-carregado no teclado.
Onde: o teclado numérico é um componente filho, e o campo de entrada utiliza este componente compartilhado.
Principais Desafios:
- Valores passados via props para o componente do teclado não podem ser modificados diretamente. É necessário utilizar propriedades computadas para essa finalidade.
- Ao compartilhar o teclado, garantir que os números não sejam misturados incorretamente. Utilizar v-for com a propriedade name e alterar os parâmetros durante o clique para atingir o objetivo.
Código Completo:
index.vue
<template>
<div class="container">
<div v-for="(valor, nome) em listaValores" :key="nome">
<div class="area-texto" v-if="nome=='CampoX'">
Valor Padrão Positivo: <input :value="valor" @click="mostrarTeclado(nome, '0')">
</div>
<div class="area-texto" v-if="nome=='CampoY'">
Valor Padrão Negativo: <input :value="valor" @click="mostrarTeclado(nome, '1')">
</div>
</div>
<TecladoNumerico :visivel="estaVisivel" :campoAtivo="campoAtivo" tamanhoFonte="20px" :ehNegativo="ehNegativo" :valorNumerico="valorNumerico" @valorDigitado="receberValor"></TecladoNumerico>
</div>
</template>
<script>
import TecladoNumerico from "../Componentes/TecladoNumerico.vue";
export default {
data() {
return {
titulo: 'Implementação Vue',
estaVisivel: false, // controla a exibição do teclado
campoAtivo: '',
ehNegativo: '0', // indica se é negativo (1 para negativo)
valorNumerico: '',
listaValores: {"CampoX": "","CampoY": ""},
}
},
components:{
TecladoNumerico
},
methods: {
receberValor(valor, nomeCampo) {
this.listaValores[this.campoAtivo] = valor;
this.estaVisivel = false;
},
mostrarTeclado(nome, indicadorNegativo){
this.estaVisivel = true;
this.campoAtivo = nome;
this.valorNumerico = this.listaValores[this.campoAtivo];
this.ehNegativo = indicadorNegativo;
},
}
}
</script>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.area-texto {
display: flex;
justify-content: center;
margin: 10px 0;
}
input {
padding: 8px;
margin-left: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
TecladoNumerico.vue
<template>
<div v-if="estaVisivel">
<div class="fundo-transparente">
<div class="visor">{{ valorFormatado }}</div>
<div class="botoes">
<div class="botao" @click="processarEntrada('1')">1</div>
<div class="botao" @click="processarEntrada('2')">2</div>
<div class="botao" @click="processarEntrada('3')">3</div>
<div class="botao" @click="processarEntrada('limpar')">Limpar</div>
<div class="botao" @click="processarEntrada('4')">4</div>
<div class="botao" @click="processarEntrada('5')">5</div>
<div class="botao" @click="processarEntrada('6')">6</div>
<div class="botao" @click="processarEntrada('apagar')">Apagar</div>
<div class="botao" @click="processarEntrada('7')">7</div>
<div class="botao" @click="processarEntrada('8')">8</div>
<div class="botao" @click="processarEntrada('9')">9</div>
<div class="botao" @click="processarEntrada('.')">.</div>
<div class="botao" @click="processarEntrada('-')">-</div>
<div class="botao" @click="processarEntrada('0')">0</div>
<div class="botao" @click="processarEntrada('+')">+</div>
<div class="botao botao-azul" @click="processarEntrada('confirmar')">OK</div>
</div>
</div>
</div>
</template>
<script>
export default {
data (){
return {
entradaAtual: '',
primeiraRenderizacao: true
}
},
props:{
estaVisivel:{
type: Boolean,
default: false
},
tamanhoFonte: {
type: String,
default: "20px"
},
ehNegativo: {
type: String,
default: "0"
},
campoAtivo:{
type: String,
default: ""
},
valorNumerico:{
type: String,
default: ""
}
},
computed: {
valorFormatado () {
if (this.primeiraRenderizacao) {
this.entradaAtual = this.valorNumerico;
}
return this.entradaAtual;
}
},
methods: {
processarEntrada(acao) {
this.primeiraRenderizacao = false;
if (acao == 'confirmar') {
// Confirmação
if ((this.entradaAtual.length == 1 && (this.entradaAtual[0] == '+' || this.entradaAtual[0] == '-')) ||
this.entradaAtual.substring(this.entradaAtual.length-1) == ".") {
return;
}
this.$emit('valorDigitado', this.entradaAtual, this.campoAtivo);
this.primeiraRenderizacao = true;
} else if (acao == 'apagar') {
// Excluir último caractere
this.entradaAtual = this.entradaAtual.substring(0, this.entradaAtual.length - 1);
} else if (acao == '+' || acao == '-') {
// Sinal positivo/negativo
if (this.entradaAtual[0] != '+' && this.entradaAtual[0] != '-') {
this.entradaAtual = acao + this.entradaAtual;
} else {
this.entradaAtual = acao + this.entradaAtual.substring(1);
}
} else if (acao == '.') {
// Ponto decimal
let contemPonto = false;
for (let i = 0; i < this.entradaAtual.length; i++) {
if (this.entradaAtual[i] == '.') {
contemPonto = true;
break;
}
}
if (this.entradaAtual.length == 0 ||
(this.entradaAtual.length == 1 && (this.entradaAtual[0] == '+' || this.entradaAtual[0] == '-'))) {
// Não permite ponto se estiver vazio ou após sinal
} else if (!contemPonto) {
this.entradaAtual += acao;
}
} else if (acao == 'limpar') {
// Limpar tudo
this.entradaAtual = '';
} else {
// Números
if (acao == "0" && this.entradaAtual.length == 0) {
// Se começar com 0, adiciona ponto
this.entradaAtual = "0.";
} else {
this.entradaAtual += acao;
}
if (this.ehNegativo == '1' && this.entradaAtual[0] != "+" && this.entradaAtual[0] != "-") {
// Define como negativo se configurado
this.entradaAtual = "-" + this.entradaAtual;
}
}
},
}
}
</script>
<style>
/* Estilo do teclado numérico */
.fundo-transparente {
width:100%;
height: 305px;
position: fixed;
bottom: 0;
left: 0;
margin: auto;
z-index: 999;
background: #f7f7f7;
border-radius: 6px;
}
.botoes {
padding: 0 15px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.botao {
width: 23%;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 20px;
background: #fff;
margin-bottom: 10px;
border-radius: 6px;
border: solid 1px #eee;
cursor: pointer;
transition: background-color 0.2s;
}
.botao:hover {
background: #efefef;
}
.visor {
width: 90%;
height: 40px;
line-height: 40px;
text-align: right;
font-size: 25px;
padding: 5px 15px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.botao-azul {
color: #fff;
background: #0078ff;
}
</style>
Atualização 2023: Utilizando o modificador .sync
O método .sync também pode ser utilizado para modificar props, como demonstrado abaixo:
Componente pai:
<TecladoNumerico :valorNumerico.sync="valorNumerico"></TecladoNumerico>
Componente filho:
// As props permanecem as mesmas
props:{
valorNumerico:{
type: String,
default: ""
}
},
// No evento de clique
this.$emit('update:valorNumerico', novoValor);