mirror of https://github.com/ElemeFE/element
Transfer (#4337)
* add transfer * Transfer: add English doc * Transfer: add tests * update locale filespull/4526/head
parent
349894d107
commit
ec3895fdba
|
@ -71,11 +71,11 @@ export default {
|
|||
},
|
||||
{
|
||||
filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),
|
||||
content: `## ${chineseName}`
|
||||
content: `## ${ComponentName} ${chineseName}`
|
||||
},
|
||||
{
|
||||
filename: path.join('../../examples/docs/en-US', `${componentname}.md`),
|
||||
content: `## ${componentname}`
|
||||
content: `## ${ComponentName}`
|
||||
},
|
||||
{
|
||||
filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),
|
||||
|
|
|
@ -61,5 +61,6 @@
|
|||
"collapse": "./packages/collapse/index.js",
|
||||
"collapse-item": "./packages/collapse-item/index.js",
|
||||
"cascader": "./packages/cascader/index.js",
|
||||
"color-picker": "./packages/color-picker/index.js"
|
||||
"color-picker": "./packages/color-picker/index.js",
|
||||
"transfer": "./packages/transfer/index.js"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `Option ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const generateData2 = _ => {
|
||||
const data = [];
|
||||
const states = ['California', 'Illinois', 'Maryland', 'Texas', 'Florida', 'Colorado', 'Connecticut '];
|
||||
const initials = ['CA', 'IL', 'MD', 'TX', 'FL', 'CO', 'CT'];
|
||||
states.forEach((city, index) => {
|
||||
data.push({
|
||||
label: city,
|
||||
key: index,
|
||||
initial: initials[index]
|
||||
});
|
||||
});
|
||||
return data;
|
||||
};
|
||||
const generateData3 = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
value: i,
|
||||
desc: `Option ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data: generateData(),
|
||||
data2: generateData2(),
|
||||
data3: generateData3(),
|
||||
value1: [1, 4],
|
||||
value2: [],
|
||||
value3: [1],
|
||||
value4: [],
|
||||
filterMethod(query, item) {
|
||||
return item.initial.toLowerCase().indexOf(query.toLowerCase()) > -1;
|
||||
},
|
||||
renderFunc(h, option) {
|
||||
return <span>{ option.key } - { option.label }</span>;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleChange(value, direction, movedKeys) {
|
||||
console.log(value, direction, movedKeys);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
## Transfer
|
||||
|
||||
### Basic usage
|
||||
:::demo Data is passed to Transfer via the `data` attribute. The data needs to be an object array, and each object should have these attributes: `key` being the identification of the data item, `label` being the displayed text, and `disabled` indicating if the data item is disabled. Items inside the target list are in sync with the variable binding to `v-model`, and the value of that variable is an array of target item keys. So, if you don't want the target list be initially empty, you can initialize the `v-model` with an array.
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
v-model="value1"
|
||||
:data="data">
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `Option ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data: generateData(),
|
||||
value1: [1, 4]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Filterable
|
||||
|
||||
You can search and filter data items.
|
||||
|
||||
:::demo Set the `filterable` attribute to `true` to enable filter mode. By default, if the data item `label` contains the search keyword, it will be included in the search result. Also, you can implement you own filter method with the `filter-method` attribute. It takes a method and passes search keyword and each data item to it whenever the keyword changes. For a certain data item, if the method returns true, it will be included in the result list.
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
filterable
|
||||
:filter-method="filterMethod"
|
||||
filter-placeholder="State Abbreviations"
|
||||
v-model="value2"
|
||||
:data="data2">
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData2 = _ => {
|
||||
const data = [];
|
||||
const states = ['California', 'Illinois', 'Maryland', 'Texas', 'Florida', 'Colorado', 'Connecticut '];
|
||||
const initials = ['CA', 'IL', 'MD', 'TX', 'FL', 'CO', 'CT'];
|
||||
states.forEach((city, index) => {
|
||||
data.push({
|
||||
label: city,
|
||||
key: index,
|
||||
initial: initials[index]
|
||||
});
|
||||
});
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data2: generateData2(),
|
||||
value2: [],
|
||||
filterMethod(query, item) {
|
||||
return item.initial.toLowerCase().indexOf(query.toLowerCase()) > -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Customizable
|
||||
|
||||
You can customize list titles, button texts, render function for data items, checking status texts in list footer and list footer contents.
|
||||
|
||||
:::demo Use `titles`, `button-texts`, `render-content` and `footer-format` to respectively customize list titles, button texts, render function for data items, checking status texts in list footer. For list footer contents, two named slots are provided: `left-footer` and `right-footer`. Plus, if you want some items initially checked, you can use `left-default-checked` and `right-default-checked`. Finally, this example demonstrate the `change` event. Note that this demo can't run in jsfiddle because it doesn't support JSX syntax. In a real project, `render-content` will work if relevant dependencies are correctly configured.
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
v-model="value3"
|
||||
filterable
|
||||
:left-default-checked="[2, 3]"
|
||||
:right-default-checked="[1]"
|
||||
:render-content="renderFunc"
|
||||
:titles="['Source', 'Target']"
|
||||
:button-texts="['To left', 'To right']"
|
||||
:footer-format="{
|
||||
noChecked: '${total}',
|
||||
hasChecked: '${checked}/${total}'
|
||||
}"
|
||||
@change="handleChange"
|
||||
:data="data">
|
||||
<el-button class="transfer-footer" slot="left-footer" size="small">Operation</el-button>
|
||||
<el-button class="transfer-footer" slot="right-footer" size="small">Operation</el-button>
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.transfer-footer {
|
||||
margin-left: 20px;
|
||||
padding: 6px 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `Option ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data: generateData(),
|
||||
value3: [1],
|
||||
renderFunc(h, option) {
|
||||
return <span>{ option.key } - { option.label }</span>;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleChange(value, direction, movedKeys) {
|
||||
console.log(value, direction, movedKeys);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Prop aliases
|
||||
|
||||
By default, Transfer looks for `key`, `label` and `disabled` in a data item. If your data items have different key names, you can use the `props` attribute to define aliases.
|
||||
:::demo The data items in this example do not have `key`s or `label`s, instead they have `value`s and `desc`s. So you need to set aliases for `key` and `label`.
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
v-model="value4"
|
||||
:props="{
|
||||
key: 'value',
|
||||
label: 'desc'
|
||||
}"
|
||||
:data="data3">
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData3 = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
value: i,
|
||||
desc: `Option ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data3: generateData3(),
|
||||
value4: []
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Attributes
|
||||
| Attribute | Description | Type | Accepted Values | Default |
|
||||
|---------- |-------- |---------- |------------- |-------- |
|
||||
| data | data source | array[{ key, label, disabled }] | — | [ ] |
|
||||
| filterable | whether Transfer is filterable | boolean | — | false |
|
||||
| filter-placeholder | placeholder for the filter input | string | — | Enter keyword |
|
||||
| filter-method | custom filter method | function | — | — |
|
||||
| titles | custom list titles | array | — | ['List 1', 'List 2'] |
|
||||
| button-texts | custom button texts | array | — | [ ] |
|
||||
| render-content | custom render function for data items | function(h, option) | — | — |
|
||||
| footer-format | texts for checking status in list footer | object{noChecked, hasChecked} | — | { noChecked: '${total} items', hasChecked: '${checked}/${total} checked' } |
|
||||
| props | prop aliases for data source | object{key, label, disabled} | — | — |
|
||||
| left-default-checked | key array of initially checked data items of the left list | array | — | [ ] |
|
||||
| right-default-checked | key array of initially checked data items of the right list | array | — | [ ] |
|
||||
|
||||
### Slot
|
||||
| Name | Description |
|
||||
|------|--------|
|
||||
| left-footer | content of left list footer |
|
||||
| right-footer | content of right list footer |
|
||||
|
||||
### Events
|
||||
| Event Name | Description | Parameters |
|
||||
|---------- |-------- |---------- |
|
||||
| change | triggers when data items change in the right list | key array of current data items in the right list, transfer direction (left or right), moved item keys |
|
|
@ -0,0 +1,281 @@
|
|||
<style>
|
||||
.demo-transfer {
|
||||
.transfer-footer {
|
||||
margin-left: 20px;
|
||||
padding: 6px 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `备选项 ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const generateData2 = _ => {
|
||||
const data = [];
|
||||
const cities = ['上海', '北京', '广州', '深圳', '南京', '西安', '成都'];
|
||||
const pinyin = ['shanghai', 'beijing', 'guangzhou', 'shenzhen', 'nanjing', 'xian', 'chengdu'];
|
||||
cities.forEach((city, index) => {
|
||||
data.push({
|
||||
label: city,
|
||||
key: index,
|
||||
pinyin: pinyin[index]
|
||||
});
|
||||
});
|
||||
return data;
|
||||
};
|
||||
const generateData3 = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
value: i,
|
||||
desc: `备选项 ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data: generateData(),
|
||||
data2: generateData2(),
|
||||
data3: generateData3(),
|
||||
value1: [1, 4],
|
||||
value2: [],
|
||||
value3: [1],
|
||||
value4: [],
|
||||
filterMethod(query, item) {
|
||||
return item.pinyin.indexOf(query) > -1;
|
||||
},
|
||||
renderFunc(h, option) {
|
||||
return <span>{ option.key } - { option.label }</span>;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleChange(value, direction, movedKeys) {
|
||||
console.log(value, direction, movedKeys);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
## Transfer 穿梭框
|
||||
|
||||
### 基础用法
|
||||
:::demo Transfer 的数据通过 `data` 属性传入。数据需要是一个对象数组,每个对象有以下属性:`key` 为数据的唯一性标识,`label` 为显示文本,`disabled` 表示该项数据是否禁止转移。目标列表中的数据项会同步到绑定至 `v-model` 的变量,值为数据项的 `key` 所组成的数组。当然,如果希望在初始状态时目标列表不为空,可以像本例一样为 `v-model` 绑定的变量赋予一个初始值。
|
||||
```html
|
||||
<template>
|
||||
<el-transfer v-model="value1" :data="data"></el-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `备选项 ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data: generateData(),
|
||||
value1: [1, 4]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### 可搜索
|
||||
|
||||
在数据很多的情况下,可以对数据进行搜索和过滤。
|
||||
|
||||
:::demo 设置 `filterable` 为 `true` 即可开启搜索模式。默认情况下,若数据项的 `label` 属性包含搜索关键字,则会在搜索结果中显示。你也可以使用 `filter-method` 定义自己的搜索逻辑。`filter-method` 接收一个方法,当搜索关键字变化时,会将当前的关键字和每个数据项传给该方法。若方法返回 `true`,则会在搜索结果中显示对应的数据项。
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
filterable
|
||||
:filter-method="filterMethod"
|
||||
filter-placeholder="请输入城市拼音"
|
||||
v-model="value2"
|
||||
:data="data2">
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData2 = _ => {
|
||||
const data = [];
|
||||
const cities = ['上海', '北京', '广州', '深圳', '南京', '西安', '成都'];
|
||||
const pinyin = ['shanghai', 'beijing', 'guangzhou', 'shenzhen', 'nanjing', 'xian', 'chengdu'];
|
||||
cities.forEach((city, index) => {
|
||||
data.push({
|
||||
label: city,
|
||||
key: index,
|
||||
pinyin: pinyin[index]
|
||||
});
|
||||
});
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data2: generateData2(),
|
||||
value2: [],
|
||||
filterMethod(query, item) {
|
||||
return item.pinyin.indexOf(query) > -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### 可自定义
|
||||
|
||||
可以对列表标题文案、按钮文案、数据项的渲染函数、列表底部的勾选状态文案、列表底部的内容区等进行自定义。
|
||||
|
||||
:::demo 可以使用 `titles`、`button-texts`、`render-content` 和 `footer-format` 属性分别对列表标题文案、按钮文案、数据项的渲染函数和列表底部的勾选状态文案进行自定义。对于列表底部的内容区,提供了两个具名 slot:`left-footer` 和 `right-footer`。此外,如果希望某些数据项在初始化时就被勾选,可以使用 `left-default-checked` 和 `right-default-checked` 属性。最后,本例还展示了 `change` 事件的用法。注意:由于 jsfiddle 不支持 JSX 语法,所以本例在 jsfiddle 中无法运行。但是在实际的项目中,只要正确地配置了相关依赖,就可以正常运行。
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
v-model="value3"
|
||||
filterable
|
||||
:left-default-checked="[2, 3]"
|
||||
:right-default-checked="[1]"
|
||||
:render-content="renderFunc"
|
||||
:titles="['Source', 'Target']"
|
||||
:button-texts="['到左边', '到右边']"
|
||||
:footer-format="{
|
||||
noChecked: '${total}',
|
||||
hasChecked: '${checked}/${total}'
|
||||
}"
|
||||
@change="handleChange"
|
||||
:data="data">
|
||||
<el-button class="transfer-footer" slot="left-footer" size="small">操作</el-button>
|
||||
<el-button class="transfer-footer" slot="right-footer" size="small">操作</el-button>
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.transfer-footer {
|
||||
margin-left: 20px;
|
||||
padding: 6px 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `备选项 ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data: generateData(),
|
||||
value3: [1],
|
||||
renderFunc(h, option) {
|
||||
return <span>{ option.key } - { option.label }</span>;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleChange(value, direction, movedKeys) {
|
||||
console.log(value, direction, movedKeys);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### 数据项属性别名
|
||||
|
||||
默认情况下,Transfer 仅能识别数据项中的 `key`、`label` 和 `disabled` 字段。如果你的数据的字段名不同,可以使用 `props` 属性为它们设置别名。
|
||||
:::demo 本例中的数据源没有 `key` 和 `label` 字段,在功能上与它们相同的字段名为 `value` 和 `desc`。因此可以使用`props` 属性为 `key` 和 `label` 设置别名。
|
||||
```html
|
||||
<template>
|
||||
<el-transfer
|
||||
v-model="value4"
|
||||
:props="{
|
||||
key: 'value',
|
||||
label: 'desc'
|
||||
}"
|
||||
:data="data3">
|
||||
</el-transfer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const generateData3 = _ => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
value: i,
|
||||
desc: `备选项 ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
return {
|
||||
data3: generateData3(),
|
||||
value4: []
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
:::
|
||||
|
||||
### Attributes
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|---------- |-------- |---------- |------------- |-------- |
|
||||
| data | Transfer 的数据源 | array[{ key, label, disabled }] | — | [ ] |
|
||||
| filterable | 是否可搜索 | boolean | — | false |
|
||||
| filter-placeholder | 搜索框占位符 | string | — | 请输入搜索内容 |
|
||||
| filter-method | 自定义搜索方法 | function | — | — |
|
||||
| titles | 自定义列表标题 | array | — | ['列表 1', '列表 2'] |
|
||||
| button-texts | 自定义按钮文案 | array | — | [ ] |
|
||||
| render-content | 自定义数据项渲染函数 | function(h, option) | — | — |
|
||||
| footer-format | 列表底部勾选状态文案 | object{noChecked, hasChecked} | — | { noChecked: '共 ${total} 项', hasChecked: '已选 ${checked}/${total} 项' } |
|
||||
| props | 数据源的字段别名 | object{key, label, disabled} | — | — |
|
||||
| left-default-checked | 初始状态下左侧列表的已勾选项的 key 数组 | array | — | [ ] |
|
||||
| right-default-checked | 初始状态下右侧列表的已勾选项的 key 数组 | array | — | [ ] |
|
||||
|
||||
### Slot
|
||||
| name | 说明 |
|
||||
|------|--------|
|
||||
| left-footer | 左侧列表底部的内容 |
|
||||
| right-footer | 右侧列表底部的内容 |
|
||||
|
||||
### Events
|
||||
| 事件名称 | 说明 | 回调参数 |
|
||||
|---------- |-------- |---------- |
|
||||
| change | 右侧列表元素变化时触发 | 当前值、数据移动的方向('left' / 'right')、发生移动的数据 key 数组 |
|
|
@ -116,6 +116,10 @@
|
|||
"path": "/color-picker",
|
||||
"title": "ColorPicker 颜色选择器"
|
||||
},
|
||||
{
|
||||
"path": "/transfer",
|
||||
"title": "Transfer 穿梭框"
|
||||
},
|
||||
{
|
||||
"path": "/form",
|
||||
"title": "Form 表单"
|
||||
|
@ -350,6 +354,10 @@
|
|||
"path": "/color-picker",
|
||||
"title": "ColorPicker"
|
||||
},
|
||||
{
|
||||
"path": "/transfer",
|
||||
"title": "Transfer"
|
||||
},
|
||||
{
|
||||
"path": "/form",
|
||||
"title": "Form"
|
||||
|
|
|
@ -589,4 +589,17 @@
|
|||
--collapse-content-fill: var(--color-dark-white);
|
||||
--collapse-content-size: 13px;
|
||||
--collapse-content-color: var(--color-base-black);
|
||||
|
||||
/* Transfer
|
||||
--------------------------*/
|
||||
--transfer-border-color: var(--color-base-gray);
|
||||
--transfer-box-shadow: var(--box-shadow-base);
|
||||
--transfer-panel-width: 200px;
|
||||
--transfer-panel-header-height: 36px;
|
||||
--transfer-panel-header-background: var(--color-dark-white);
|
||||
--transfer-panel-footer-height: 36px;
|
||||
--transfer-panel-body-height: 246px;
|
||||
--transfer-item-height: 32px;
|
||||
--transfer-item-hover-background: var(--color-light-gray);
|
||||
--transfer-filter-height: 22px;
|
||||
}
|
||||
|
|
|
@ -59,3 +59,4 @@
|
|||
@import "./collapse-item.css";
|
||||
@import "./cascader.css";
|
||||
@import "./color-picker.css";
|
||||
@import "./transfer.css";
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
@charset "UTF-8";
|
||||
@import "./common/var.css";
|
||||
@import "./input.css";
|
||||
@import "./button.css";
|
||||
@import "./checkbox.css";
|
||||
@import "./checkbox-group.css";
|
||||
|
||||
@component-namespace el {
|
||||
@b transfer {
|
||||
font-size: var(--font-size-base);
|
||||
|
||||
@e buttons {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0 10px;
|
||||
|
||||
.el-button {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding: 8px 12px;
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button [class*="el-icon-"] + span {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@b transfer-panel {
|
||||
border: 1px solid var(--transfer-border-color);
|
||||
background: var(--color-white);
|
||||
box-shadow: var(--transfer-box-shadow);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: var(--transfer-panel-width);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
@e body {
|
||||
padding-bottom: var(--transfer-panel-footer-height);
|
||||
height: var(--transfer-panel-body-height);
|
||||
}
|
||||
|
||||
@e list {
|
||||
margin: 0;
|
||||
padding: 6px 0;
|
||||
list-style: none;
|
||||
height: var(--transfer-panel-body-height);
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
@when filterable {
|
||||
height: calc(var(--transfer-panel-body-height) - var(--transfer-filter-height) - 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@e item {
|
||||
height: var(--transfer-item-height);
|
||||
line-height: var(--transfer-item-height);
|
||||
padding-left: 20px;
|
||||
display: block;
|
||||
|
||||
& + .el-transfer-panel__item {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&.el-checkbox {
|
||||
color: var(--color-extra-light-black);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--transfer-item-hover-background);
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
width: 100%;
|
||||
@utils-ellipsis;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.el-checkbox__input {
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@e filter {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-input__inner {
|
||||
height: var(--transfer-filter-height);
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.el-input__icon {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.el-icon-circle-close {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.el-transfer-panel__header {
|
||||
height: var(--transfer-panel-header-height);
|
||||
line-height: var(--transfer-panel-header-height);
|
||||
background: var(--transfer-panel-header-background);
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
border-bottom: 1px solid var(--transfer-border-color);
|
||||
box-sizing: border-box;
|
||||
color: var(--color-base-black);
|
||||
}
|
||||
|
||||
.el-transfer-panel__footer {
|
||||
height: var(--transfer-panel-footer-height);
|
||||
background: var(--color-white);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top: 1px solid var(--transfer-border-color);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: var(--index-normal);
|
||||
@utils-vertical-center;
|
||||
|
||||
.el-checkbox {
|
||||
padding-left: 20px;
|
||||
color: var(--color-base-silver);
|
||||
}
|
||||
}
|
||||
|
||||
.el-transfer-panel__empty {
|
||||
margin: 0;
|
||||
height: var(--transfer-item-height);
|
||||
line-height: var(--transfer-item-height);
|
||||
padding: 6px 20px 0;
|
||||
color: var(--color-base-silver);
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.el-checkbox__inner {
|
||||
size: 14px;
|
||||
border-radius: 3px;
|
||||
&::after {
|
||||
height: 6px;
|
||||
width: 3px;
|
||||
left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import Transfer from './src/main';
|
||||
|
||||
/* istanbul ignore next */
|
||||
Transfer.install = function(Vue) {
|
||||
Vue.component(Transfer.name, Transfer);
|
||||
};
|
||||
|
||||
export default Transfer;
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<div class="el-transfer">
|
||||
<transfer-panel
|
||||
:filterable="filterable"
|
||||
:filter-method="filterMethod"
|
||||
:data="sourceData"
|
||||
:render-content="renderContent"
|
||||
:title="titles[0] || t('el.transfer.titles.0')"
|
||||
:format="footerFormat"
|
||||
:default-checked="leftDefaultChecked"
|
||||
:placeholder="filterPlaceholder || t('el.transfer.filterPlaceholder')"
|
||||
:props="props"
|
||||
@checked-change="onSourceCheckedChange">
|
||||
<slot name="left-footer"></slot>
|
||||
</transfer-panel>
|
||||
<div class="el-transfer__buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click.native="addToLeft"
|
||||
:disabled="rightChecked.length === 0">
|
||||
<i class="el-icon-arrow-left"></i>
|
||||
<span v-if="buttonTexts[0] !== undefined">{{ buttonTexts[0] }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click.native="addToRight"
|
||||
:disabled="leftChecked.length === 0">
|
||||
<span v-if="buttonTexts[1] !== undefined">{{ buttonTexts[1] }}</span>
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
<transfer-panel
|
||||
:filterable="filterable"
|
||||
:filter-method="filterMethod"
|
||||
:data="targetData"
|
||||
:render-content="renderContent"
|
||||
:title="titles[1] || t('el.transfer.titles.1')"
|
||||
:format="footerFormat"
|
||||
:default-checked="rightDefaultChecked"
|
||||
:placeholder="filterPlaceholder || t('el.transfer.filterPlaceholder')"
|
||||
:props="props"
|
||||
@checked-change="onTargetCheckedChange">
|
||||
<slot name="right-footer"></slot>
|
||||
</transfer-panel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ElButton from 'element-ui/packages/button';
|
||||
import Emitter from 'element-ui/src/mixins/emitter';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
import TransferPanel from './transfer-panel.vue';
|
||||
|
||||
export default {
|
||||
name: 'ElTransfer',
|
||||
|
||||
mixins: [Emitter, Locale],
|
||||
|
||||
components: {
|
||||
TransferPanel,
|
||||
ElButton
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
titles: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
buttonTexts: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
filterPlaceholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
filterMethod: Function,
|
||||
leftDefaultChecked: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
rightDefaultChecked: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
renderContent: Function,
|
||||
value: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
footerFormat: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
filterable: Boolean,
|
||||
props: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
label: 'label',
|
||||
key: 'key',
|
||||
disabled: 'disabled'
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
leftChecked: [],
|
||||
rightChecked: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
sourceData() {
|
||||
return this.data.filter(item => this.value.indexOf(item[this.props.key]) === -1);
|
||||
},
|
||||
|
||||
targetData() {
|
||||
return this.data.filter(item => this.value.indexOf(item[this.props.key]) > -1);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.dispatch('ElFormItem', 'el.form.change', val);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSourceCheckedChange(val) {
|
||||
this.leftChecked = val;
|
||||
},
|
||||
|
||||
onTargetCheckedChange(val) {
|
||||
this.rightChecked = val;
|
||||
},
|
||||
|
||||
addToLeft() {
|
||||
let currentValue = this.value.slice();
|
||||
this.rightChecked.forEach(item => {
|
||||
const index = currentValue.indexOf(item);
|
||||
if (index > -1) {
|
||||
currentValue.splice(index, 1);
|
||||
}
|
||||
});
|
||||
this.$emit('input', currentValue);
|
||||
this.$emit('change', currentValue, 'left', this.rightChecked);
|
||||
},
|
||||
|
||||
addToRight() {
|
||||
let currentValue = this.value.slice();
|
||||
this.leftChecked.forEach(item => {
|
||||
if (this.value.indexOf(item) === -1) {
|
||||
currentValue = currentValue.concat(item);
|
||||
}
|
||||
});
|
||||
this.$emit('input', currentValue);
|
||||
this.$emit('change', currentValue, 'right', this.leftChecked);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,229 @@
|
|||
<template>
|
||||
<div class="el-transfer-panel">
|
||||
<p class="el-transfer-panel__header">{{ title }}</p>
|
||||
|
||||
<div class="el-transfer-panel__body">
|
||||
<el-input
|
||||
class="el-transfer-panel__filter"
|
||||
v-model="query"
|
||||
size="small"
|
||||
:placeholder="placeholder"
|
||||
:icon="inputIcon"
|
||||
@mouseenter.native="inputHover = true"
|
||||
@mouseleave.native="inputHover = false"
|
||||
@click="clearQuery"
|
||||
v-if="filterable"></el-input>
|
||||
<el-checkbox-group
|
||||
v-model="checked"
|
||||
v-show="!hasNoMatch && data.length > 0"
|
||||
:class="{ 'is-filterable': filterable }"
|
||||
class="el-transfer-panel__list">
|
||||
<el-checkbox
|
||||
class="el-transfer-panel__item"
|
||||
:label="item[keyProp]"
|
||||
:disabled="item[disabledProp]"
|
||||
v-for="item in filteredData">
|
||||
<option-content :option="item"></option-content>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<p
|
||||
class="el-transfer-panel__empty"
|
||||
v-show="hasNoMatch">{{ t('el.transfer.noMatch') }}</p>
|
||||
<p
|
||||
class="el-transfer-panel__empty"
|
||||
v-show="data.length === 0 && !hasNoMatch">{{ t('el.transfer.noData') }}</p>
|
||||
</div>
|
||||
|
||||
<p class="el-transfer-panel__footer">
|
||||
<el-checkbox
|
||||
v-model="allChecked"
|
||||
@change="handleAllCheckedChange"
|
||||
:indeterminate="isIndeterminate">{{ checkedSummary }}</el-checkbox>
|
||||
<slot></slot>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ElCheckboxGroup from 'element-ui/packages/checkbox-group';
|
||||
import ElCheckbox from 'element-ui/packages/checkbox';
|
||||
import ElInput from 'element-ui/packages/input';
|
||||
import Locale from 'element-ui/src/mixins/locale';
|
||||
|
||||
export default {
|
||||
mixins: [Locale],
|
||||
|
||||
name: 'ElTransferPanel',
|
||||
|
||||
componentName: 'ElTransferPanel',
|
||||
|
||||
components: {
|
||||
ElCheckboxGroup,
|
||||
ElCheckbox,
|
||||
ElInput,
|
||||
OptionContent: {
|
||||
props: {
|
||||
option: Object
|
||||
},
|
||||
render(h) {
|
||||
const getParent = vm => {
|
||||
if (vm.$options.componentName === 'ElTransferPanel') {
|
||||
return vm;
|
||||
} else if (vm.$parent) {
|
||||
return getParent(vm.$parent);
|
||||
} else {
|
||||
return vm;
|
||||
}
|
||||
};
|
||||
const parent = getParent(this);
|
||||
return parent.renderContent
|
||||
? parent.renderContent(h, this.option)
|
||||
: <span>{ this.option[parent.labelProp] || this.option[parent.keyProp] }</span>;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
renderContent: Function,
|
||||
placeholder: String,
|
||||
title: String,
|
||||
filterable: Boolean,
|
||||
format: Object,
|
||||
filterMethod: Function,
|
||||
defaultChecked: Array,
|
||||
props: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
checked: [],
|
||||
allChecked: false,
|
||||
query: '',
|
||||
inputHover: false
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
checked(val) {
|
||||
this.updateAllChecked();
|
||||
this.$emit('checked-change', val);
|
||||
},
|
||||
|
||||
data() {
|
||||
const checked = [];
|
||||
const filteredDataKeys = this.filteredData.map(item => item[this.keyProp]);
|
||||
this.checked.forEach(item => {
|
||||
if (filteredDataKeys.indexOf(item) > -1) {
|
||||
checked.push(item);
|
||||
}
|
||||
});
|
||||
this.checked = checked;
|
||||
},
|
||||
|
||||
checkableData() {
|
||||
this.updateAllChecked();
|
||||
},
|
||||
|
||||
defaultChecked: {
|
||||
immediate: true,
|
||||
handler(val, oldVal) {
|
||||
if (oldVal && val.length === oldVal.length &&
|
||||
val.every(item => oldVal.indexOf(item) > -1)) return;
|
||||
const checked = [];
|
||||
const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);
|
||||
val.forEach(item => {
|
||||
if (checkableDataKeys.indexOf(item) > -1) {
|
||||
checked.push(item);
|
||||
}
|
||||
});
|
||||
this.checked = checked;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredData() {
|
||||
return this.data.filter(item => {
|
||||
if (typeof this.filterMethod === 'function') {
|
||||
return this.filterMethod(this.query, item);
|
||||
} else {
|
||||
const label = item[this.labelProp] || item[this.keyProp].toString();
|
||||
return label.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
checkableData() {
|
||||
return this.filteredData.filter(item => !item[this.disabledProp]);
|
||||
},
|
||||
|
||||
checkedSummary() {
|
||||
const checkedLength = this.checked.length;
|
||||
const dataLength = this.data.length;
|
||||
const { noChecked, hasChecked } = this.format;
|
||||
if (noChecked && hasChecked) {
|
||||
return checkedLength > 0
|
||||
? hasChecked.replace(/\${checked}/g, checkedLength).replace(/\${total}/g, dataLength)
|
||||
: noChecked.replace(/\${total}/g, dataLength);
|
||||
} else {
|
||||
return checkedLength > 0
|
||||
? this.t('el.transfer.hasCheckedFormat', { total: dataLength, checked: checkedLength })
|
||||
: this.t('el.transfer.noCheckedFormat', { total: dataLength });
|
||||
}
|
||||
},
|
||||
|
||||
isIndeterminate() {
|
||||
const checkedLength = this.checked.length;
|
||||
return checkedLength > 0 && checkedLength < this.checkableData.length;
|
||||
},
|
||||
|
||||
hasNoMatch() {
|
||||
return this.query.length > 0 && this.filteredData.length === 0;
|
||||
},
|
||||
|
||||
inputIcon() {
|
||||
return this.query.length > 0 && this.inputHover
|
||||
? 'circle-close'
|
||||
: 'search';
|
||||
},
|
||||
|
||||
labelProp() {
|
||||
return this.props.label || 'label';
|
||||
},
|
||||
|
||||
keyProp() {
|
||||
return this.props.key || 'key';
|
||||
},
|
||||
|
||||
disabledProp() {
|
||||
return this.props.disabled || 'disabled';
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateAllChecked() {
|
||||
const checkableDataKeys = this.checkableData.map(item => item[this.keyProp]);
|
||||
this.allChecked = checkableDataKeys.length > 0 &&
|
||||
checkableDataKeys.every(item => this.checked.indexOf(item) > -1);
|
||||
},
|
||||
|
||||
handleAllCheckedChange(value) {
|
||||
this.checked = value.target.checked
|
||||
? this.checkableData.map(item => item[this.keyProp])
|
||||
: [];
|
||||
},
|
||||
|
||||
clearQuery() {
|
||||
if (this.inputIcon === 'circle-close') {
|
||||
this.query = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -63,6 +63,7 @@ import Collapse from '../packages/collapse/index.js';
|
|||
import CollapseItem from '../packages/collapse-item/index.js';
|
||||
import Cascader from '../packages/cascader/index.js';
|
||||
import ColorPicker from '../packages/color-picker/index.js';
|
||||
import Transfer from '../packages/transfer/index.js';
|
||||
import locale from 'element-ui/src/locale';
|
||||
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
|
||||
|
||||
|
@ -125,6 +126,7 @@ const components = [
|
|||
Collapse,
|
||||
CollapseItem,
|
||||
Cascader,
|
||||
Transfer,
|
||||
ColorPicker,
|
||||
CollapseTransition
|
||||
];
|
||||
|
@ -223,5 +225,6 @@ module.exports = {
|
|||
Collapse,
|
||||
CollapseItem,
|
||||
Cascader,
|
||||
ColorPicker
|
||||
ColorPicker,
|
||||
Transfer
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Няма данни'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Няма намерени',
|
||||
noData: 'Няма данни',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -89,6 +89,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Sense Dades'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'No hi ha dades que coincideixin',
|
||||
noData: 'Sense Dades',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -92,6 +92,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Žádná data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Žádná shoda',
|
||||
noData: 'Žádná data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -89,6 +89,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Ingen data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Ingen matchende data',
|
||||
noData: 'Ingen data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -91,6 +91,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Keine Daten'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Nichts gefunden.',
|
||||
noData: 'Keine Datei',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -60,6 +60,10 @@ export default {
|
|||
noData: 'Χωρίς δεδομένα',
|
||||
placeholder: 'Επιλογή'
|
||||
},
|
||||
cascader: {
|
||||
noMatch: 'Δεν βρέθηκαν αποτελέσματα',
|
||||
placeholder: 'Επιλογή'
|
||||
},
|
||||
pagination: {
|
||||
goto: 'Μετάβαση σε',
|
||||
pagesize: '/σελίδα',
|
||||
|
@ -86,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Χωρίς Δεδομένα'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Δεν βρέθηκαν αποτελέσματα',
|
||||
noData: 'Χωρίς δεδομένα',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'No Data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'No matching data',
|
||||
noData: 'No data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -89,6 +89,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Sin Datos'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'No hay datos que coincidan',
|
||||
noData: 'Sin datos',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'اطلاعاتی وجود ندارد'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'هیچ دادهای پیدا نشد',
|
||||
noData: 'اطلاعاتی وجود ندارد',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -60,6 +60,10 @@ export default {
|
|||
noData: 'Ei tietoja',
|
||||
placeholder: 'Valitse'
|
||||
},
|
||||
cascader: {
|
||||
noMatch: 'Ei vastaavia tietoja',
|
||||
placeholder: 'Valitse'
|
||||
},
|
||||
pagination: {
|
||||
goto: 'Mene',
|
||||
pagesize: '/sivu',
|
||||
|
@ -86,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Ei tietoja'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Ei vastaavia tietoja',
|
||||
noData: 'Ei tietoja',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
select: {
|
||||
loading: 'Chargement',
|
||||
noMatch: 'Aucune correspondance',
|
||||
noData: 'Aucun résultat',
|
||||
noData: 'Aucune donnée',
|
||||
placeholder: 'Choisir'
|
||||
},
|
||||
cascader: {
|
||||
|
@ -89,6 +89,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Aucune donnée'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Aucune correspondance',
|
||||
noData: 'Aucune donnée',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Tidak Ada Data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Tidak ada data yang cocok',
|
||||
noData: 'Tidak ada data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
select: {
|
||||
loading: 'Caricamento',
|
||||
noMatch: 'Nessuna corrispondenza',
|
||||
noData: 'Nessun risultato',
|
||||
noData: 'Nessun dato',
|
||||
placeholder: 'Seleziona'
|
||||
},
|
||||
cascader: {
|
||||
|
@ -89,6 +89,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Nessun dato'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Nessuna corrispondenza',
|
||||
noData: 'Nessun dato',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'データなし'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'データなし',
|
||||
noData: 'データなし',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: '데이터 없음'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: '맞는 데이터가 없습니다',
|
||||
noData: '데이터 없음',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -89,6 +89,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Ingen Data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Ingen samsvarende data',
|
||||
noData: 'Ingen data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Geen data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Geen overeenkomende resultaten',
|
||||
noData: 'Geen data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Brak danych'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Brak dopasowań',
|
||||
noData: 'Brak danych',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Sem dados'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Sem resultados',
|
||||
noData: 'Sem dados',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Sem dados'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Sem correspondência',
|
||||
noData: 'Sem dados',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Нет данных'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Совпадений не найдено',
|
||||
noData: 'Нет данных',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -62,6 +62,10 @@ export default {
|
|||
noData: 'Žiadne dáta',
|
||||
placeholder: 'Vybrať'
|
||||
},
|
||||
cascader: {
|
||||
noMatch: 'Žiadna zhoda',
|
||||
placeholder: 'Vybrať'
|
||||
},
|
||||
pagination: {
|
||||
goto: 'Choď na',
|
||||
pagesize: 'na stranu',
|
||||
|
@ -88,6 +92,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Žiadne dáta'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Žiadna zhoda',
|
||||
noData: 'Žiadne dáta',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -60,6 +60,10 @@ export default {
|
|||
noData: 'Ingen data',
|
||||
placeholder: 'Välj'
|
||||
},
|
||||
cascader: {
|
||||
noMatch: 'Hittade inget',
|
||||
placeholder: 'Välj'
|
||||
},
|
||||
pagination: {
|
||||
goto: 'Gå till',
|
||||
pagesize: '/sida',
|
||||
|
@ -86,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Inga Data'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Hittade inget',
|
||||
noData: 'Ingen data',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'ไม่พบข้อมูล'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'ไม่พบข้อมูลที่ตรงกัน',
|
||||
noData: 'ไม่พบข้อมูล',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Maglumat ýok'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Hiçzat tapylmady',
|
||||
noData: 'Hiçzat ýok',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Veri yok'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Eşleşen veri bulunamadı',
|
||||
noData: 'Veri yok',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Нема даних'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Співпадінь не знайдено',
|
||||
noData: 'Обрати',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: 'Không có dữ liệu'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: 'Dữ liệu không phù hợp',
|
||||
noData: 'Không tìm thấy dữ liệu',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: '暂无数据'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: '无匹配数据',
|
||||
noData: '无数据',
|
||||
titles: ['列表 1', '列表 2'],
|
||||
filterPlaceholder: '请输入搜索内容',
|
||||
noCheckedFormat: '共 {total} 项',
|
||||
hasCheckedFormat: '已选 {checked}/{total} 项'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -90,6 +90,14 @@ export default {
|
|||
},
|
||||
tree: {
|
||||
emptyText: '暫無資料'
|
||||
},
|
||||
transfer: {
|
||||
noMatch: '無匹配資料',
|
||||
noData: '無資料',
|
||||
titles: ['List 1', 'List 2'], // to be translated
|
||||
filterPlaceholder: 'Enter keyword', // to be translated
|
||||
noCheckedFormat: '{total} items', // to be translated
|
||||
hasCheckedFormat: '{checked}/{total} checked' // to be translated
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import { createTest, createVue, destroyVM } from '../util';
|
||||
import Transfer from 'packages/transfer';
|
||||
|
||||
describe('Transfer', () => {
|
||||
let vm;
|
||||
const getTestData = () => {
|
||||
const data = [];
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `备选项 ${ i }`,
|
||||
disabled: i % 4 === 0
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const createTransfer = (props, opts) => {
|
||||
return createVue(Object.assign({
|
||||
template: `
|
||||
<el-transfer :data="testData" ref="transfer" ${props}>
|
||||
</el-transfer>
|
||||
`,
|
||||
|
||||
created() {
|
||||
this.testData = getTestData();
|
||||
}
|
||||
}, opts));
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
destroyVM(vm);
|
||||
});
|
||||
|
||||
it('create', () => {
|
||||
vm = createTest(Transfer, true);
|
||||
expect(vm.$el).to.exist;
|
||||
});
|
||||
|
||||
it('default target list', () => {
|
||||
vm = createTransfer('v-model="value"', {
|
||||
data() {
|
||||
return {
|
||||
value: [1, 4]
|
||||
};
|
||||
}
|
||||
});
|
||||
expect(vm.$refs.transfer.sourceData.length).to.equal(13);
|
||||
});
|
||||
|
||||
it('filterable', done => {
|
||||
vm = createTransfer('v-model="value" filterable :filter-method="method"', {
|
||||
data() {
|
||||
return {
|
||||
value: [],
|
||||
method(query, option) {
|
||||
return option.key === Number(query);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
const transfer = vm.$refs.transfer;
|
||||
const leftList = transfer.$el.querySelector('.el-transfer-panel').__vue__;
|
||||
leftList.query = '1';
|
||||
setTimeout(_ => {
|
||||
expect(leftList.filteredData.length).to.equal(1);
|
||||
done();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('transfer', done => {
|
||||
vm = createTransfer('v-model="value" :left-default-checked="[2, 3]" :right-default-checked="[1]"', {
|
||||
data() {
|
||||
return {
|
||||
value: [1, 4]
|
||||
};
|
||||
}
|
||||
});
|
||||
const transfer = vm.$refs.transfer;
|
||||
|
||||
setTimeout(_ => {
|
||||
transfer.addToLeft();
|
||||
setTimeout(_ => {
|
||||
expect(transfer.sourceData.length).to.equal(14);
|
||||
transfer.addToRight();
|
||||
setTimeout(_ => {
|
||||
expect(transfer.sourceData.length).to.equal(12);
|
||||
done();
|
||||
}, 50);
|
||||
}, 50);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('customize', () => {
|
||||
vm = createTransfer('v-model="value" :titles="titles" :render-content="renderFunc" :footer-format="format"', {
|
||||
data() {
|
||||
return {
|
||||
value: [2],
|
||||
titles: ['1', '2'],
|
||||
format: { noChecked: 'no', hasChecked: 'has' },
|
||||
renderFunc(h, option) {
|
||||
return <span>{ option.key } - { option.label }</span>;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
const transfer = vm.$refs.transfer.$el;
|
||||
expect(transfer.querySelector('.el-transfer-panel__header').innerText).to.equal('1');
|
||||
expect(transfer.querySelector('.el-checkbox__label span').innerText).to.equal('1 - 备选项 1');
|
||||
expect(transfer.querySelector('.el-transfer-panel__footer .el-checkbox__label').innerText).to.equal('no');
|
||||
});
|
||||
|
||||
it('check', () => {
|
||||
vm = createTransfer('v-model="value"', {
|
||||
data() {
|
||||
return {
|
||||
value: []
|
||||
};
|
||||
}
|
||||
});
|
||||
const leftList = vm.$refs.transfer.$el.querySelector('.el-transfer-panel').__vue__;
|
||||
leftList.handleAllCheckedChange({ target: { checked: true } });
|
||||
expect(leftList.checked.length).to.equal(12);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue