transfer-panel.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <template>
  2. <div class="el-transfer-panel">
  3. <p class="el-transfer-panel__header">
  4. <el-checkbox
  5. v-model="allChecked"
  6. @change="handleAllCheckedChange"
  7. :indeterminate="isIndeterminate">
  8. {{ title }}
  9. <span>{{ checkedSummary }}</span>
  10. </el-checkbox>
  11. </p>
  12. <div :class="['el-transfer-panel__body', hasFooter ? 'is-with-footer' : '']">
  13. <el-input
  14. class="el-transfer-panel__filter"
  15. v-model="query"
  16. size="small"
  17. :placeholder="placeholder"
  18. @mouseenter.native="inputHover = true"
  19. @mouseleave.native="inputHover = false"
  20. v-if="filterable">
  21. <i slot="prefix"
  22. :class="['el-input__icon', 'el-icon-' + inputIcon]"
  23. @click="clearQuery"
  24. ></i>
  25. </el-input>
  26. <el-checkbox-group
  27. v-model="checked"
  28. v-show="!hasNoMatch && data.length > 0"
  29. :class="{ 'is-filterable': filterable }"
  30. class="el-transfer-panel__list">
  31. <el-checkbox
  32. class="el-transfer-panel__item"
  33. :label="item[keyProp]"
  34. :disabled="item[disabledProp]"
  35. :key="item[keyProp]"
  36. v-for="item in filteredData">
  37. <option-content :option="item"></option-content>
  38. </el-checkbox>
  39. </el-checkbox-group>
  40. <p
  41. class="el-transfer-panel__empty"
  42. v-show="hasNoMatch">{{ t('el.transfer.noMatch') }}</p>
  43. <p
  44. class="el-transfer-panel__empty"
  45. v-show="data.length === 0 && !hasNoMatch">{{ t('el.transfer.noData') }}</p>
  46. </div>
  47. <p class="el-transfer-panel__footer" v-if="hasFooter">
  48. <slot></slot>
  49. </p>
  50. </div>
  51. </template>
  52. <script>
  53. import ElCheckboxGroup from 'element-ui/packages/checkbox-group';
  54. import ElCheckbox from 'element-ui/packages/checkbox';
  55. import ElInput from 'element-ui/packages/input';
  56. import Locale from 'element-ui/src/mixins/locale';
  57. export default {
  58. mixins: [Locale],
  59. name: 'ElTransferPanel',
  60. componentName: 'ElTransferPanel',
  61. components: {
  62. ElCheckboxGroup,
  63. ElCheckbox,
  64. ElInput,
  65. OptionContent: {
  66. props: {
  67. option: Object
  68. },
  69. render(h) {
  70. const getParent = vm => {
  71. if (vm.$options.componentName === 'ElTransferPanel') {
  72. return vm;
  73. } else if (vm.$parent) {
  74. return getParent(vm.$parent);
  75. } else {
  76. return vm;
  77. }
  78. };
  79. const panel = getParent(this);
  80. const transfer = panel.$parent || panel;
  81. return panel.renderContent
  82. ? panel.renderContent(h, this.option)
  83. : transfer.$scopedSlots.default
  84. ? transfer.$scopedSlots.default({ option: this.option })
  85. : <span class="111">{ this.option[panel.labelProp] || this.option[panel.keyProp] }</span>;
  86. }
  87. }
  88. },
  89. props: {
  90. data: {
  91. type: Array,
  92. default() {
  93. return [];
  94. }
  95. },
  96. renderContent: Function,
  97. placeholder: String,
  98. title: String,
  99. filterable: Boolean,
  100. format: Object,
  101. filterMethod: Function,
  102. defaultChecked: Array,
  103. props: Object
  104. },
  105. data() {
  106. return {
  107. checked: [],
  108. allChecked: false,
  109. query: '',
  110. inputHover: false,
  111. checkChangeByUser: true
  112. };
  113. },
  114. watch: {
  115. checked(val, oldVal) {
  116. this.updateAllChecked();
  117. if (this.checkChangeByUser) {
  118. const movedKeys = val.concat(oldVal)
  119. .filter(v => val.indexOf(v) === -1 || oldVal.indexOf(v) === -1);
  120. this.$emit('checked-change', val, movedKeys);
  121. } else {
  122. this.$emit('checked-change', val);
  123. this.checkChangeByUser = true;
  124. }
  125. },
  126. data() {
  127. const checked = [];
  128. const filteredDataKeys = this.filteredData.map(item => item[this.keyProp]);
  129. this.checked.forEach(item => {
  130. if (filteredDataKeys.indexOf(item) > -1) {
  131. checked.push(item);
  132. }
  133. });
  134. this.checkChangeByUser = false;
  135. this.checked = checked;
  136. },
  137. checkableData() {
  138. this.updateAllChecked();
  139. },
  140. defaultChecked: {
  141. immediate: true,
  142. handler(val, oldVal) {
  143. if (oldVal && val.length === oldVal.length &&
  144. val.every(item => oldVal.indexOf(item) > -1)) return;
  145. const checked = [];
  146. const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);
  147. val.forEach(item => {
  148. if (checkableDataKeys.indexOf(item) > -1) {
  149. checked.push(item);
  150. }
  151. });
  152. this.checkChangeByUser = false;
  153. this.checked = checked;
  154. }
  155. }
  156. },
  157. computed: {
  158. filteredData() {
  159. return this.data.filter(item => {
  160. if (typeof this.filterMethod === 'function') {
  161. return this.filterMethod(this.query, item);
  162. } else {
  163. const label = item[this.labelProp] || item[this.keyProp].toString();
  164. return label.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
  165. }
  166. });
  167. },
  168. checkableData() {
  169. return this.filteredData.filter(item => !item[this.disabledProp]);
  170. },
  171. checkedSummary() {
  172. const checkedLength = this.checked.length;
  173. const dataLength = this.data.length;
  174. const { noChecked, hasChecked } = this.format;
  175. if (noChecked && hasChecked) {
  176. return checkedLength > 0
  177. ? hasChecked.replace(/\${checked}/g, checkedLength).replace(/\${total}/g, dataLength)
  178. : noChecked.replace(/\${total}/g, dataLength);
  179. } else {
  180. return `${ checkedLength }/${ dataLength }`;
  181. }
  182. },
  183. isIndeterminate() {
  184. const checkedLength = this.checked.length;
  185. return checkedLength > 0 && checkedLength < this.checkableData.length;
  186. },
  187. hasNoMatch() {
  188. return this.query.length > 0 && this.filteredData.length === 0;
  189. },
  190. inputIcon() {
  191. return this.query.length > 0 && this.inputHover
  192. ? 'circle-close'
  193. : 'search';
  194. },
  195. labelProp() {
  196. return this.props.label || 'label';
  197. },
  198. keyProp() {
  199. return this.props.key || 'key';
  200. },
  201. disabledProp() {
  202. return this.props.disabled || 'disabled';
  203. },
  204. hasFooter() {
  205. return !!this.$slots.default;
  206. }
  207. },
  208. methods: {
  209. updateAllChecked() {
  210. const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);
  211. this.allChecked = checkableDataKeys.length > 0 &&
  212. checkableDataKeys.every(item => this.checked.indexOf(item) > -1);
  213. },
  214. handleAllCheckedChange(value) {
  215. this.checked = value
  216. ? this.checkableData.map(item => item[this.keyProp])
  217. : [];
  218. },
  219. clearQuery() {
  220. if (this.inputIcon === 'circle-close') {
  221. this.query = '';
  222. }
  223. }
  224. }
  225. };
  226. </script>