You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

本地可多选线上仅单选:Checkbox组绑定数组在线环境异常

多选列筛选在生产环境仅能单选的排查与解决

问题现象

  • 本地开发环境(localhost):基于Checkbox组的列筛选功能正常,可勾选多个选项显示对应列,操作状态通过LocalStorage持久化。
  • 线上生产环境:每次仅能勾选一个Checkbox,勾选第二个时第一个自动取消,疑似绑定的selectedFieldKeys变量未被识别为数组。

效果对比

本地效果:
本地多选正常效果

线上效果:
线上仅能单选异常效果

相关代码

Template部分

<template>
  <b-row class="d-none d-sm-block" v-if="selectedPropertyId && tableLayout">
    <b-col class="mb-3">
      <b-form-checkbox-group inline v-model="selectedFieldKeys" :options="selectableFields" text-field="label" value-field="key" />
    </b-col>
  </b-row>

  <b-row class="d-sm-none" v-if="selectedPropertyId && tableLayout">
    <b-col class="mb-3">
      <b-form-checkbox-group stacked v-model="selectedFieldKeys" :options="selectableFields" text-field="label" value-field="key" />
    </b-col>
  </b-row>

  <b-table
    v-if="selectedPropertyId && tableLayout"
    :items="units"
    :fields="fields"
    :filter="filterList"
    striped
    :empty-text="$t('emptyText')"
    :empty-filtered-text="$t('emptyFilteredText')"
    search-placeholder="searchInList"
    :per-page="[500, 1000]"
    sort-by="unitName"
    primary-key="dummyId"
    small="small"
    foot-clone
    clear-foot-clone
    no-footer-sorting
    sticky-header="67vh"
    include-filter
  />
</template>

Script部分

<script>
import moment from 'moment';
import LoadingMessage from '@/components/LoadingMessage';
import {listService} from '@/services/lists';
import {propertyService} from '@/services/property';

const localStorageKeyName = 'visibleFieldNames';

export default {
  data() {
    return {
      selectedFieldKeys: [],
      selectedPropertyId: null,
      loadingList: false,
      properties: [],
      units: null,
      allUnits: null
    };
  },
  props: {
    initialPropertyId: {
      type: String,
      default: null
    }
  },
  methods: {
    load() {
      propertyService.list()
        .then(result => this.properties = result.data)
        .catch(error => console.error(error));
    },
    getList() {
      if (this.selectedPropertyId) {
        this.units = null;
        this.allUnits = null;
        this.loadingList = true;
        const propertyId = this.selectedPropertyId === 'all' ? null : this.selectedPropertyId;
        listService.rentalListReport(propertyId)
          .then(result => {
            this.units = result.data;
            this.allUnits = this.units;
            this.loadingList = false;
          })
          .catch(error => {
            console.error(error);
            this.loadingList = false;
          });
      }
    },
    normalColumn(key, label) {
      return {
        key,
        label: this.$t(label ?? key),
        sortable: true
      };
    },
    enumColumn(key, prefix, label) {
      return Object.assign(this.normalColumn(key, label), {
        formatter: value => value && this.$t(`${prefix}.${value}`),
        sortByFormatted: true
      });
    },
    numberColumn(key, label) {
      return Object.assign(this.normalColumn(key, label), {
        template: {
          type: 'number',
          format: {
            minimumFractionDigits: '2',
            maximumFractionDigits: '2'
          },
          foot: sum
        }
      });
    },
    dateColumn(key, label) {
      return Object.assign(this.normalColumn(key, label), {
        template: {type: 'date', format: 'L'}
      });
    }
  },
  computed: {
    selectableFields() {
      return [
        {
          key: 'unitName',
          label: this.$t('unit'),
          sortable: true,
          template: {
            type: 'link',
            label: data => data.item.unitName,
            to: data => `/units/${data.item.unitId}`,
            foot: count
          }
        },
        this.normalColumn('stageName', 'stage'),
        this.normalColumn('unitNumber'),
        this.enumColumn('status', 'model.unit.status'),
        this.normalColumn('numberWaitingList', 'waitingList'),
        this.normalColumn('reserved'),
        this.enumColumn('unitType', 'model.unit.types', 'type'),
        this.normalColumn('internalType'),
        this.dateColumn('availableDate'),
        this.dateColumn('rentEndDate'),
        this.numberColumn('privateArea', 'model.unit.fields.PRIVATE_AREA'),
        this.numberColumn('commercialArea', 'model.unit.fields.COMMERCIAL_AREA'),
        this.numberColumn('numberRooms', 'model.unit.fields.NUMBER_ROOMS'),
        this.enumColumn('rentalPeriod', 'model.rentalPeriod'),
        this.numberColumn('rentOnAvailableDate', 'rentWithoutVat'),
        this.numberColumn('rentVatOnAvailableDate', 'rentVat'),
        this.numberColumn('rentPerYearPerM2OnAvailableDate', 'rentPerYearPerM2WithoutVat'),
        this.numberColumn('rentPerYearPerM2VatOnAvailableDate', 'rentPerYearPerM2WithVat'),
        this.numberColumn('depositOnAvailableDate', 'depositWithoutVat'),
        this.numberColumn('depositVatOnAvailableDate', 'depositWithVat'),
        this.numberColumn('prepaymentOnAvailableDate', 'prepaymentWithoutVat'),
        this.numberColumn('prepaymentVatOnAvailableDate', 'prepaymentWithVat'),
        this.numberColumn('acontoOnAvailableDate', 'acontoWithoutVat'),
        this.numberColumn('acontoVatOnAvailableDate','acontoWithVat'),
      ];
    },
    fields() {
      return this.selectableFields.filter(field => this.selectedFieldKeys?.includes(field.key)).concat(
        {
          key: 'unitUnavailable',
          label: '',
          sortable: true,
          template: {
            type: 'status'
          }
        },
        {
          key: 'actions',
          template: {
            type: 'actions',
            head: [
              {
                type: 'export'
              }
            ],
            cell: this.actions
          }
        }
      )
    }
  },
  created() {
    this.selectedPropertyId = this.initialPropertyId;
    try {
      this.selectedFieldKeys = JSON.parse(window.localStorage.getItem(localStorageKeyName));
    } catch {
      this.selectedFieldKeys = this.selectableFields.map(field => field.key);
    }
  },
  watch: {
    selectedPropertyId: 'getList',
    selectedFieldKeys: {
      deep: true,
      handler() {
        window.localStorage.setItem(localStorageKeyName, JSON.stringify(this.selectedFieldKeys));
      }
    }
  }
};
</script>

排查与解决

1. 修复LocalStorage初始化的类型校验

线上环境可能存在旧的非数组格式的LocalStorage数据(比如之前误存的单个字符串),导致selectedFieldKeys被赋值为非数组,Bootstrap-Vue的Checkbox组会自动切换为单选模式。修改created钩子中的初始化逻辑,强制校验数组类型:

created() {
  this.selectedPropertyId = this.initialPropertyId;
  try {
    const savedData = JSON.parse(window.localStorage.getItem(localStorageKeyName));
    // 确保赋值的是数组,否则用默认全选
    this.selectedFieldKeys = Array.isArray(savedData) ? savedData : this.selectableFields.map(field => field.key);
  } catch {
    this.selectedFieldKeys = this.selectableFields.map(field => field.key);
  }
}

2. 显式设置Checkbox组的multiple属性

虽然Bootstrap-Vue的b-form-checkbox-group默认是多选模式,但线上打包优化可能丢失默认配置,显式添加multiple属性确保多选行为:

<b-form-checkbox-group inline v-model="selectedFieldKeys" :options="selectableFields" text-field="label" value-field="key" multiple />

3. 确保响应式更新正常

避免直接替换数组引用,改用Vue的数组方法或splice来更新,确保响应式触发:

// 初始化时用splice替代直接赋值
const defaultKeys = this.selectableFields.map(field => field.key);
this.selectedFieldKeys.splice(0, this.selectedFieldKeys.length, ...(Array.isArray(savedData) ? savedData : defaultKeys));

4. 检查打包压缩配置

如果使用Webpack/Vite等打包工具,检查是否有过度压缩导致数组逻辑异常。可以在selectedFieldKeys定义处添加注释禁止压缩:

data() {
  return {
    /* webpackMode: "eager" */
    selectedFieldKeys: [],
    // ...其他数据
  };
}

内容的提问来源于stack exchange,提问作者Scandidi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 17:37:05