Imlpementación de edición inline con autocompletado
Cuando se trabaja con tablas que requieren edición directa en las celdas, es común necesitar funcionalidades como autocompletado para campos específicos. Este ejemplo muestra cómo integrar el-autocomplete dentro de una columna de el-table.
<!-- Definición de columnas dinámicas -->
<jr-table-column
:label="col.label"
v-for="col in headers"
:key="col.field">
<template slot-scope="context">
<template v-if="mode === 'editing'">
<!-- Campo de autocompletado para nombres -->
<el-autocomplete
v-if="col.field === 'partyName'"
popper-class="custom-suggestions"
style="width: 100%"
clearable
ref="autoCompleteRefs"
v-model="context.row[col.field]"
:fetch-suggestions="loadSuggestions"
placeholder="Nombre del participante"
@select="(selected) => onSelect(selected, context.$index)">
<el-button
slot="append"
style="width: 60px"
:loading="searching"
@click="triggerSearch(context.$index)">
<i class="el-icon-search" v-if="!searching"></i>
</el-button>
</el-autocomplete>
<!-- Otros campos como inputs simples -->
<el-input
v-else
v-model="context.row[col.field]"
size="mini"/>
</template>
<template v-else>
{{ context.row[col.field] }}
</template>
</template>
</jr-table-column>
data() {
return {
headers: [
{ field: "date", label: "Fecha" },
{ field: "partyType", label: "Tipo" },
{ field: "partyName", label: "Nombre" },
{ field: "idCode", label: "Código ID" },
{ field: "investment", label: "Inversión" }
],
mode: "",
currentQuery: "",
searching: false,
cacheResults: false,
suggestionsData: []
};
},
methods: {
loadSuggestions(input, callback) {
this.currentQuery = input;
if (this.cacheResults) {
callback(this.suggestionsData);
this.cacheResults = false;
} else {
callback(this.suggestionsData || []);
}
},
onSelect(selection, rowIndex) {
const record = this.tableRows[rowIndex];
record.partyCode = selection.code;
record.idCode = selection.idNumber;
},
triggerSearch(rowIndex) {
const params = {
name: this.currentQuery,
type: this.tableRows[rowIndex].partyType || ""
};
this.searching = true;
searchParties(params).then(response => {
this.cacheResults = true;
this.searching = false;
this.suggestionsData = response.data.map(item => ({
code: item.code,
idNumber: item.documentId,
value: item.name
}));
if (!this.suggestionsData.length) {
this.$message.warning("No se encontraron coincidencias");
} else {
// Acceso al elemento correcto considerando duplicados en refs
const targetIndex = rowIndex * 2;
this.$refs.autoCompleteRefs[targetIndex].focus();
}
});
}
}
Ejemplo completo de tabla editable
<template>
<div>
<div class="toolbar">
<el-button type="primary" size="mini" @click="enterEditMode">
Editar
</el-button>
<el-button type="success" size="mini" @click="saveChanges">
Guardar
</el-button>
</div>
<jr-table :showPagination="false" height="300" :tableData="tableRows">
<jr-table-column label="#" type="index" width="50"></jr-table-column>
<jr-table-column
:label="column.label"
v-for="column in headers"
:key="column.field">
<template slot-scope="ctx">
<template v-if="mode === 'editing'">
<el-autocomplete
v-if="column.field === 'partyName'"
popper-class="suggestions-popup"
style="width: 100%"
clearable
ref="autoRefs"
v-model="ctx.row[column.field]"
:fetch-suggestions="loadSuggestions"
placeholder="Ingrese nombre"
@select="(sel) => onSelect(sel, ctx.$index)">
<el-button
slot="append"
style="width: 60px"
:loading="searching"
@click="triggerSearch(ctx.$index)">
<i class="el-icon-search" v-if="!searching"></i>
</el-button>
</el-autocomplete>
<el-input
v-else
v-model="ctx.row[column.field]"
size="mini"/>
</template>
<template v-else>
<span>{{ ctx.row[column.field] }}</span>
</template>
</template>
</jr-table-column>
<el-table-column fixed="right" label="Acciones" width="190" align="center">
<template slot-scope="ctx">
<el-button icon="el-icon-plus" size="mini" type="primary"
@click="addNewRow(ctx.$index)">
Añadir
</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete"
@click="removeRow(ctx.$index)">
Eliminar
</el-button>
</template>
</el-table-column>
</jr-table>
</div>
</template>
<script>
import { updateRecords, searchParties } from "@/services/api";
export default {
props: {
tableRows: Array
},
data() {
return {
headers: [
{ field: "date", label: "Fecha" },
{ field: "partyType", label: "Tipo Participante" },
{ field: "partyName", label: "Nombre" },
{ field: "idCode", label: "Identificación" },
{ field: "investment", label: "Monto Invertido" }
],
mode: "",
currentQuery: "",
searching: false,
useCache: false,
suggestionsData: []
};
},
methods: {
loadSuggestions(query, cb) {
this.currentQuery = query;
cb(this.useCache ? this.suggestionsData : []);
this.useCache = false;
},
onSelect(option, idx) {
const row = this.tableRows[idx];
row.partyCode = option.code;
row.idCode = option.idNumber;
},
triggerSearch(idx) {
const request = {
partyName: this.currentQuery,
partyType: this.tableRows[idx].partyType || ""
};
this.searching = true;
searchParties(request).then(res => {
this.useCache = true;
this.searching = false;
this.suggestionsData = res.data.map(item => ({
code: item.partyCode,
idNumber: item.taxId,
value: item.fullName
}));
if (!this.suggestionsData.length) {
this.$message.warning("Sin resultados disponibles");
} else {
this.$refs.autoRefs[idx * 2].focus();
}
});
},
saveChanges() {
if (this.tableRows?.length) {
updateRecords(this.tableRows).then(() => {
this.mode = "";
this.$emit("onSave");
this.$message.success("Datos guardados correctamente");
});
} else {
this.$message.warning("No hay datos para guardar");
}
},
enterEditMode() {
if (!this.tableRows?.length) {
this.tableRows.push(this.createEmptyRecord());
}
this.mode = "editing";
},
addNewRow() {
this.mode = "editing";
this.suggestionsData = [];
this.tableRows.push(this.createEmptyRecord());
},
removeRow(index) {
this.$confirm("¿Eliminar este registro?", "Confirmación", {
confirmButtonText: "Sí",
cancelButtonText: "Cancelar",
type: "warning"
}).then(() => {
this.tableRows.splice(index, 1);
this.$message.success("Registro eliminado");
});
},
createEmptyRecord() {
return {
date: "",
partyType: "",
partyName: "",
partyCode: "",
idCode: "",
investment: ""
};
}
}
};
</script>
Nota: Al usar componentes anidados dentro de bucles de tabla, Vue genera múltiples referencias por cada fila renderizada. Es importante calcular el índice correcto al acceder meidante $refs.