V-Model 不会使用 Switch 和 Select 下拉菜单在 Vuejs 中更改数据



我在 laravel 和 vue 的帮助下制作了一个带有 crud 生成器的动态管理面板。

我有一个表,我正在从 API 异步加载数据。我的表中有一个is_featured列,我想把它作为一个开关。以便用户可以从表页面更改值,而不是转到编辑页面。

要生成我的整个表,有一个配置对象,其中包含要显示的字段以及该字段的类型和其他元数据。在配置对象中,有一个名为prerender的字段,它负责预呈现需要调用其他 API 的字段或一些可编辑字段,如选择下拉列表或开关。

为了使开关和选择字段正常工作,我有一个名为fieldData的空对象。 当找到具有type: booleanprerender: true的字段时,我的代码将在字段数据中初始化一个属性,并将 field.name 作为属性名称,并使用该字段名称下的相应值填充该属性

this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);

但是开关和选择在这里不起作用

所以我需要帮助。

这是我的完整代码供参考

<template>
<div class="app-container">
<el-row :gutter="20" style="display: flex; align-items: center;">
<el-col :span="10">
<h1 style="text-transform: capatilize;">{{ resourceName }}</h1>
</el-col>
<el-col :span="14" style="display: flex; justify-content: flex-end; align-items: center">
<el-input
v-model="navigation.search"
placeholder="Search anything here"
prefix-icon="el-icon-search"
style="width: 300px; margin: 0 10px;"
@keydown.enter.native="handleGlobalSearch"
/>
<FilterPannel
style="margin: 0 10px"
:filter-pannel-obj="filterPannelObj"
@set-filter="handleFilterVals"
@reset-filter="getTableData({})"
/>
<Import
:url="`/api/${resourceName}/upload`"
@import-success="handleImportSucces"
@import-error="handleImportError"
/>
<Export :url="`/api/${resourceName}/export`" :selected-ids="selected.map(el => el.id)" />
<el-button
type="info"
icon="el-icon-delete"
@click="$refs['table'].clearSelection()"
>Clear Selection</el-button>
<el-button type="danger" icon="el-icon-delete" @click="handleMultipleDelete">Delete Selected</el-button>
</el-col>
</el-row>
<el-row>
<el-table
ref="table"
v-loading="loading.tableData"
:data="tableData"
border
:row-key="getRowKeys"
@sort-change="handleSortChange"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" label="Selection" reserve-selection />
<el-table-column label="Actions" width="200">
<template slot-scope="scope">
<div style="display: flex; justify-content: space-around;">
<el-button
icon="el-icon-view"
type="primary"
circle
@click="$router.push(`/${resourceName}/view/${scope.row.id}`)"
/>
<el-button
icon="el-icon-edit"
type="success"
circle
@click="$router.push(`/${resourceName}/edit/${scope.row.id}`)"
/>
<el-button
icon="el-icon-delete"
type="danger"
circle
@click="handleDeleteClick(scope.row.id)"
/>
</div>
</template>
</el-table-column>
<el-table-column
v-for="field in fieldsToShow"
:key="field.name"
:prop="field.name"
:label="field.name.replace('_',' ')"
sortable="custom"
>
<template slot-scope="scope">
<div
v-if="field.type=='multilangtext'"
class="cell"
>{{ JSON.parse(scope.row[field.name])[$store.state.app.language] }}</div>
<div v-if="field.type=='text'" class="cell">{{ scope.row[field.name] }}</div>
<el-tag v-if="field.type=='tag'">{{ scope.row.type }}</el-tag>
<img v-if="field.type=='image'" :src="scope.row.icon" width="100px" height="100px" />
<div v-if="field.type=='oneFrom'" class="cell">
<el-tag
:key="scope.row[field.name]"
>{{field.multilang ? scope.row[field.name][$store.state.app.language] : scope.row[field.name]}}</el-tag>
</div>
<div v-if="field.type=='manyFrom'" class="cell">
<el-tag
style="margin: 5px"
v-for="(item, index) in scope.row[field.name]"
:key="`${field.multilang ? item[$store.state.app.language] : item}+${index}`"
>{{field.multilang ? item[$store.state.app.language] : item}}</el-tag>
</div>
<div v-if="field.type=='boolean'" class="cell">
{{scope.row.id}} =>
{{field.name}} =>>
{{scope.row[field.name]}} =>>>
{{fieldData[field.name][scope.row.id]}}
<el-switch
:key="`switch${scope.row.id}`"
v-model="fieldData[field.name][scope.row.id]"
:active-value="1"
:inactive-value="0"
></el-switch>
</div>
<div v-if="field.type=='select'" class="cell">
<el-select :key="`select${scope.row.id}`" v-model="fieldData[field.name][scope.row.id]" placeholder="Select">
<el-option
v-for="item in field.options"
:key="item"
:label="item"
:value="item"
></el-option>
</el-select>
</div>
</template>
</el-table-column>
</el-table>
</el-row>
<el-row>
<pagination
style="padding: 0;"
:total="paginationData.total"
:page.sync="paginationData.current_page"
:limit.sync="paginationData.per_page"
@pagination="handlePagination"
/>
</el-row>
</div>
</template>
<script>
import Pagination from '@/components/Pagination';
import FilterPannel from '@/components/FilterPannel';
import Export from './components/Export'; // ? not needed
import Import from './components/Import';
import axios from 'axios';
import Resource from '@/api/resource';
const resourceName = 'coupons';
const ResourceApi = new Resource(resourceName);
export default {
name: 'CategoryList',
components: {
Pagination,
FilterPannel,
Export,
Import,
},
data() {
return {
resourceName: resourceName,
language: 'en',
tableData: [],
fieldData: {},
fieldsToShow: [
{ name: 'title', type: 'multilangtext' },
// { name: 'description', type: 'multilangtext' },
{ name: 'code', type: 'text' },
// { name: 'expiry_date', type: 'text' },
{ name: 'type', type: 'tag' },
{
name: 'store_id',
type: 'oneFrom',
url: '/api/stores/',
foreignKey: 'store_id',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'tags',
type: 'manyFrom',
url: '/api/tags?idsarr=',
foreignKey: 'tags',
attrName: 'name',
multilang: true,
prerender: true,
},
{
name: 'brands',
type: 'manyFrom',
url: '/api/brands?idsarr=',
foreignKey: 'brands',
attrName: 'name',
multilang: true,
prerender: true,
},
// { name: 'brands', type: 'text' },
{ name: 'is_featured', type: 'boolean', prerender: true },
{
name: 'status',
type: 'select',
options: ['publish', 'draft', 'trash'],
prerender: true,
},
],
paginationData: {
current_page: 0,
last_page: 0,
per_page: 0,
total: 0,
},
navigation: {
page: 1,
limit: 10,
sort: '',
'sort-order': 'asc',
filters: '',
search: '',
},
filters: '',
selected: [], // ? for selection
loading: {
tableData: false,
},
allData: [],
filterPannelObj: {
title: {
default: '',
type: 'Input',
label: 'Title',
},
description: {
default: '',
type: 'Input',
label: 'Description',
},
promo_text: {
default: '',
type: 'Input',
label: 'Promo Text',
},
type: {
default: [],
type: 'checkbox',
label: 'Type',
src: [
{ value: 'coupon', label: 'Coupon' },
{ value: 'offer', label: 'Offer' },
],
},
code: {
default: '',
type: 'Input',
label: 'Code',
},
store_id: {
default: [],
type: 'select',
label: 'Store',
src: [],
multiple: true,
},
brands: {
default: [],
type: 'select',
label: 'Brands',
src: [],
multiple: true,
},
tags: {
default: [],
type: 'select',
label: 'Tags',
src: [],
multiple: true,
},
cats: {
default: [],
type: 'select',
label: 'Categories',
src: [],
multiple: true,
},
},
};
},
watch: {
async 'navigation.search'(newVal, oldVal) {
await this.handleGlobalSearch();
},
},
async created() {
await this.getTableData({});
this.allData = await ResourceApi.list({ limit: -1 });
// to fill filter dialog selects
// To get brands
this.filterPannelObj.brands.src = (await axios.get(
`/api/brands?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get tags
this.filterPannelObj.tags.src = (await axios.get(
`/api/tags?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get categories
this.filterPannelObj.cats.src = (await axios.get(
`/api/categories?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
// To get stores
this.filterPannelObj.store_id.src = (await axios.get(
`/api/stores?limit=-1`
)).data.map(({ name, id }) => ({
label: JSON.parse(name)[this.$store.state.app.language],
value: id,
}));
},
methods: {
printScope(x) {
console.log('TCL: printScope -> x', x);
},
async getTableData(query) {
this.loading.tableData = true;
const responseData = await ResourceApi.list(query);
this.tableData = responseData.data;
this.paginationData = this.pick(
['current_page', 'last_page', 'per_page', 'total'],
responseData
);
Object.keys(this.paginationData).forEach(
key => (this.paginationData[key] = parseInt(this.paginationData[key]))
);
await this.handlePrerender();
this.loading.tableData = false;
},
async handlePrerender() {
this.fieldsToShow.forEach(async field => {
if (field.prerender) {
switch (field.type) {
case 'oneFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'manyFrom': {
await this.setRelatedFieldName(field);
break;
}
case 'boolean': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
case 'select': {
this.fieldData[field.name] = {};
this.tableData.forEach(
data => (this.fieldData[field.name][data.id] = data[field.name])
);
break;
}
}
}
});
},
// utils
pick(propsArr, srcObj) {
return Object.keys(srcObj).reduce((obj, k) => {
if (propsArr.includes(k)) {
obj[k] = srcObj[k];
}
return obj;
}, {});
},
// ? remember to refactor the parameter id
async setRelatedFieldName({
name,
type,
url,
foreignKey,
attrName,
multilang,
}) {
this.tableData.forEach(async data => {
if (type === 'oneFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data[
attrName
];
if (multilang) {
data[name] = JSON.parse(data[name]);
}
} else if (type === 'manyFrom') {
data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data.map(
idata => (multilang ? JSON.parse(idata[attrName]) : idata[attrName])
);
}
});
},
// Sort
async handleSortChange(change) {
this.navigation.sort = change.prop;
if (change.order === 'ascending') {
this.navigation['sort-order'] = 'asc';
} else if (change.order === 'descending') {
this.navigation['sort-order'] = 'desc';
}
await this.getTableData(this.navigation);
},
// Pagination
async handlePagination(obj) {
// obj page obj containing {page: ..., limit: ...}
this.navigation.page = obj.page;
this.navigation.limit = obj.limit;
await this.getTableData(this.navigation);
},
// Global Search
async handleGlobalSearch() {
await this.getTableData(this.navigation);
},
// ? Skipped for now
// Filters
async handleFilterVals(filterparams) {
console.log('TCL: handleFilterVals -> filterparams', filterparams);
this.navigation.filters = JSON.stringify(filterparams.filters);
this.navigation.sort = filterparams.sort.field;
this.navigation.sort = 'name';
this.navigation['sort-order'] = filterparams.sort.asc ? 'asc' : 'desc';
await this.getTableData(this.navigation);
},
async handleDeleteClick(id) {
ResourceApi.destroy(id)
.then(res => {
this.$message.success('Delete Successfully');
this.getTableData({ page: this.paginationData.current_page });
})
.error(err => {
this.$message.error(err);
});
},
// Selection methods
handleSelectionChange(selection) {
this.selected = selection;
},
getRowKeys(row) {
return row.id;
},
// Multiple Delete
handleMultipleDelete() {
axios
.delete(
`/api/${this.resourceName}/delete-multiple?ids=${this.selected
.map(item => item.id)
.join(',')}`
)
.then(async () => {
this.$message.success('Records deleted successfully');
await this.getTableData({ page: this.paginationData.current_page });
if (this.tableData.length === 0) {
await this.getTableData({
page: this.paginationData.current_page,
});
}
this.$refs.table.clearSelection();
})
.catch();
},
// Import Events
handleImportSucces() {
this.$message.success('New Data Imported');
this.getTableData({});
},
handleImportError(err) {
this.$message.error('There were some errors. CHK console');
console.log(err);
},
},
};
</script>
<style lang="scss" scoped>
</style>

我想这是一个反应性问题。dict且实际上不影响feildData字典的值。

变化-

this.fieldData[field.name] = {};

将其替换为

self.$set(self.fieldData,field.name,{});

this.fieldData[field.name][data.id] = data[field.name] // to
//Replace
self.$set(self.fieldData[field.name],data.id, data[field.name]);

我认为这将解决问题。而不是使用 = 使用 $set 进行值分配。

代码笔 - https://codepen.io/Pratik__007/pen/gObGOKx

最新更新