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: 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" >
          <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 中的下拉菜单的主要内容,如果未能解决你的问题,请参考以下文章

VueJS:输入动态值+ v-model

VueJS 从子级更改 v-model 变量

v-model data() 上的 Vue JS 调用函数更改

vuejs

VueJs更新数据问题时对象类型道具不改变

由于 v-model,Vue js 选择选项未出现