v-model 不使用开关更改数据并选择 vuejs 中的下拉菜单
Posted
技术标签:
【中文标题】v-model 不使用开关更改数据并选择 vuejs 中的下拉菜单【英文标题】:v-model is not changing data with switch and select dropdown in vuejs 【发布时间】:2020-04-19 03:53:27 【问题描述】:在 laravel 和 vue 的帮助下,我正在使用 crud 生成器制作一个动态管理面板。
我有一个表,我从 API 异步加载数据。我的表中有一个 is_featured 列,我想成为一个开关。这样用户就可以从表格页面更改值,而不是去编辑页面。
要生成我的整个表格,有一个配置对象,其中包含要显示的字段以及该字段的类型和其他元数据。在配置对象中,有一个名为prerender
的字段,负责预渲染需要调用其他API的字段或一些可编辑的字段,如选择下拉菜单或开关。
为了使切换和选择字段起作用,我有一个名为fieldData
的空对象。
当找到具有type: boolean
和prerender: 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" >
<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" />
<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>
【问题讨论】:
【参考方案1】:我想这是一个反应性问题。 dict
的值实际上不影响feildData
dict。
变化 -
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 进行值分配。
Codepen - https://codepen.io/Pratik__007/pen/gObGOKx
【讨论】:
以上是关于v-model 不使用开关更改数据并选择 vuejs 中的下拉菜单的主要内容,如果未能解决你的问题,请参考以下文章