Browse Source

Select: add value-key

Leopoldthecoder 8 năm trước cách đây
mục cha
commit
40a873e93e

+ 1 - 0
.gitignore

@@ -12,3 +12,4 @@ examples/pages/zh-CN
 fe.element/element-ui
 .npmrc
 coverage
+waiter.config.js

+ 5 - 0
examples/docs/en-US/select.md

@@ -637,11 +637,16 @@ Create and select new items that are not included in select options
 ```
 :::
 
+:::tip
+If the binding value of Select is an object, make sure to assign `value-key` as its unique identity key name.
+:::
+
 ### Select Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | multiple | whether multiple-select is activated | boolean | — | false |
 | disabled | whether Select is disabled | boolean | — | false |
+| value-key | unique identity key name for value, required when value is an object | string | — | value |
 | size | size of Input | string | large/small/mini | — |
 | clearable | whether single select can be cleared | boolean | — | false |
 | multiple-limit | maximum number of options user can select when `multiple` is `true`. No limit when set to 0 | number | — | 0 |

+ 5 - 0
examples/docs/zh-CN/select.md

@@ -632,11 +632,16 @@
 ```
 :::
 
+:::tip
+如果 Select 的绑定值为对象类型,请务必指定 `value-key` 作为它的唯一性标识。
+:::
+
 ### Select Attributes 
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | multiple | 是否多选 | boolean | — | false |
 | disabled | 是否禁用 | boolean | — | false |
+| value-key | 作为 value 唯一标识的键名,绑定值为对象类型时必填 | string | — | value |
 | size | 输入框尺寸 | string | large/small/mini | — |
 | clearable | 单选时是否可以清空选项 | boolean | — | false |
 | multiple-limit | 多选时用户最多可以选择的项目数,为 0 则不限制 | number | — | 0 |

+ 29 - 3
packages/select/src/option.vue

@@ -17,6 +17,7 @@
 
 <script type="text/babel">
   import Emitter from 'element-ui/src/mixins/emitter';
+  import { getValueByPath } from 'element-ui/src/utils/util';
 
   export default {
     mixins: [Emitter],
@@ -47,8 +48,13 @@
     },
 
     computed: {
+      isObject() {
+        const type = typeof this.value;
+        return type !== 'string' && type !== 'number';
+      },
+
       currentLabel() {
-        return this.label || ((typeof this.value === 'string' || typeof this.value === 'number') ? this.value : '');
+        return this.label || (this.isObject ? '' : this.value);
       },
 
       currentValue() {
@@ -65,9 +71,9 @@
 
       itemSelected() {
         if (!this.parent.multiple) {
-          return this.value === this.parent.value;
+          return this.isEqual(this.value, this.parent.value);
         } else {
-          return this.parent.value.indexOf(this.value) > -1;
+          return this.contains(this.parent.value, this.value);
         }
       },
 
@@ -92,6 +98,26 @@
     },
 
     methods: {
+      isEqual(a, b) {
+        if (!this.isObject) {
+          return a === b;
+        } else {
+          const valueKey = this.parent.valueKey;
+          return getValueByPath(a, valueKey) === getValueByPath(b, valueKey);
+        }
+      },
+
+      contains(arr = [], target) {
+        if (!this.isObject) {
+          return arr.indexOf(target) > -1;
+        } else {
+          const valueKey = this.parent.valueKey;
+          return arr.some(item => {
+            return getValueByPath(item, valueKey) === getValueByPath(target, valueKey);
+          });
+        }
+      },
+
       handleGroupDisabled(val) {
         this.groupDisabled = val;
       },

+ 44 - 5
packages/select/src/select.vue

@@ -11,7 +11,7 @@
       <transition-group @after-leave="resetInputHeight">
         <el-tag
           v-for="item in selected"
-          :key="item.value"
+          :key="getValueKey(item)"
           closable
           :hit="item.hitState"
           type="primary"
@@ -104,6 +104,8 @@
   import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
   import { t } from 'element-ui/src/locale';
   import scrollIntoView from 'element-ui/src/utils/scroll-into-view';
+  import { getValueByPath } from 'element-ui/src/utils/util';
+
   const sizeMap = {
     'large': 42,
     'small': 30,
@@ -193,7 +195,11 @@
           return t('el.select.placeholder');
         }
       },
-      defaultFirstOption: Boolean
+      defaultFirstOption: Boolean,
+      valueKey: {
+        type: String,
+        default: 'value'
+      }
     },
 
     data() {
@@ -359,15 +365,20 @@
 
       getOption(value) {
         let option;
+        const type = typeof value;
+        const isObject = type !== 'string' && type !== 'number';
         for (let i = this.cachedOptions.length - 1; i >= 0; i--) {
           const cachedOption = this.cachedOptions[i];
-          if (cachedOption.value === value) {
+          const isEqual = isObject
+            ? getValueByPath(cachedOption.value, this.valueKey) === getValueByPath(value, this.valueKey)
+            : cachedOption.value === value;
+          if (isEqual) {
             option = cachedOption;
             break;
           }
         }
         if (option) return option;
-        const label = typeof value === 'string' || typeof value === 'number'
+        const label = !isObject
           ? value : '';
         let newOption = {
           value: value,
@@ -497,7 +508,7 @@
       handleOptionSelect(option) {
         if (this.multiple) {
           const value = this.value.slice();
-          const optionIndex = value.indexOf(option.value);
+          const optionIndex = this.getValueIndex(value, option.value);
           if (optionIndex > -1) {
             value.splice(optionIndex, 1);
           } else if (this.multipleLimit <= 0 || value.length < this.multipleLimit) {
@@ -516,6 +527,25 @@
         this.$nextTick(() => this.scrollToOption());
       },
 
+      getValueIndex(arr = [], value) {
+        const type = typeof value;
+        const isObject = type !== 'string' && type !== 'number';
+        if (!isObject) {
+          return arr.indexOf(value);
+        } else {
+          const valueKey = this.valueKey;
+          let index = -1;
+          arr.some((item, i) => {
+            if (getValueByPath(item, valueKey) === getValueByPath(value, valueKey)) {
+              index = i;
+              return true;
+            }
+            return false;
+          });
+          return index;
+        }
+      },
+
       toggleMenu() {
         if (this.filterable && this.query === '' && this.visible) {
           return;
@@ -626,6 +656,15 @@
             }
           }
         }
+      },
+
+      getValueKey(item) {
+        const type = typeof item.value;
+        if (type === 'number' || type === 'string') {
+          return item.value;
+        } else {
+          return getValueByPath(item.value, this.valueKey);
+        }
       }
     },
 

+ 1 - 1
packages/table/src/table-column.js

@@ -1,7 +1,7 @@
 import ElCheckbox from 'element-ui/packages/checkbox';
 import ElTag from 'element-ui/packages/tag';
 import objectAssign from 'element-ui/src/utils/merge';
-import { getValueByPath } from './util';
+import { getValueByPath } from 'element-ui/src/utils/util';
 
 let columnIdSeed = 1;
 

+ 2 - 18
packages/table/src/util.js

@@ -1,3 +1,5 @@
+import { getValueByPath } from 'element-ui/src/utils/util';
+
 export const getCell = function(event) {
   let cell = event.target;
 
@@ -11,24 +13,6 @@ export const getCell = function(event) {
   return null;
 };
 
-export const getValueByPath = function(object, prop) {
-  prop = prop || '';
-  const paths = prop.split('.');
-  let current = object;
-  let result = null;
-  for (let i = 0, j = paths.length; i < j; i++) {
-    const path = paths[i];
-    if (!current) break;
-
-    if (i === j - 1) {
-      result = current[path];
-      break;
-    }
-    current = current[path];
-  }
-  return result;
-};
-
 const isObject = function(obj) {
   return obj !== null && typeof obj === 'object';
 };

+ 18 - 0
src/utils/util.js

@@ -19,3 +19,21 @@ export function toObject(arr) {
   }
   return res;
 };
+
+export const getValueByPath = function(object, prop) {
+  prop = prop || '';
+  const paths = prop.split('.');
+  let current = object;
+  let result = null;
+  for (let i = 0, j = paths.length; i < j; i++) {
+    const path = paths[i];
+    if (!current) break;
+
+    if (i === j - 1) {
+      result = current[path];
+      break;
+    }
+    current = current[path];
+  }
+  return result;
+};