This commit is contained in:
maplemei 2019-09-27 19:10:51 +08:00
parent cdce32f11f
commit 6feee42b47
21 changed files with 871 additions and 251 deletions

View File

@ -1,5 +1,22 @@
## 更新日志 ## 更新日志
### 1.0.4
*2019-09-27*
#### 新增
- 新增多选上限设置, 可以设置多选选中上限
- 新增工具条, 可以全选, 清空, 自定义
- 新增name设置, 可以表单提交, 隐藏input实现, 值为value逗号分隔
- 新增getValue参数, 可以获取不同类型的值
#### Bug fixes
- 修复搜索模式下输入中文不显示的问题
- 修改render不能及时渲染, 需要二次渲染的问题
- 修改IE下输入循环触发input事件的问题, IE 慎入
### 1.0.3 ### 1.0.3

2
dist/static/2.js vendored

File diff suppressed because one or more lines are too long

2
dist/static/3.js vendored

File diff suppressed because one or more lines are too long

8
dist/static/docs.js vendored

File diff suppressed because one or more lines are too long

2
dist/xm-select.js vendored

File diff suppressed because one or more lines are too long

70
docs/mds/XM18.md Normal file
View File

@ -0,0 +1,70 @@
## 多选上限
### 最多选择2个
:::demo
```html
<div id="demo1" class="xm-select-demo"></div>
<script>
var demo1 = xmSelect.render({
el: '#demo1',
max: 2,
data: [
{name: '张三', value: 1, selected: true},
{name: '李四', value: 2, selected: true},
{name: '王五', value: 3},
]
})
</script>
```
:::
### 自定义选超的提示
:::demo
```html
<div id="demo2" class="xm-select-demo"></div>
<script>
var demo2 = xmSelect.render({
el: '#demo2',
max: 2,
maxMethod(seles, item){
alert(`${item.name}不能选了, 已经超了`)
},
data: [
{name: '张三', value: 1, selected: true},
{name: '李四', value: 2, selected: true},
{name: '王五', value: 3},
]
})
</script>
```
:::
### 自定义选超的闪烁颜色
:::demo
```html
<div id="demo3" class="xm-select-demo"></div>
<script>
var demo3 = xmSelect.render({
el: '#demo3',
max: 2,
theme: {
maxColor: 'orange',
},
data: [
{name: '张三', value: 1, selected: true},
{name: '李四', value: 2, selected: true},
{name: '王五', value: 3},
]
})
</script>
```
:::

111
docs/mds/XM19.md Normal file
View File

@ -0,0 +1,111 @@
## 工具条
### 默认工具条
:::demo
```html
<div id="demo1" class="xm-select-demo"></div>
<script>
var demo1 = xmSelect.render({
el: '#demo1',
toolbar: {
show: true,
},
filterable: true,
paging: true,
pageSize: 3,
data: [
{name: '张三', value: 1, selected: true},
{name: '李四', value: 2, selected: true},
{name: '王五', value: 3},
{name: '赵六', value: 4},
{name: '苹果', value: 5},
{name: '香蕉', value: 6},
{name: '凤梨', value: 7},
{name: '葡萄', value: 8},
{name: '樱桃', value: 9},
{name: '车厘子', value: 10},
{name: '火龙果', value: 11},
]
})
</script>
```
:::
### 隐藏图标
:::demo
```html
<div id="demo2" class="xm-select-demo"></div>
<script>
var demo2 = xmSelect.render({
el: '#demo2',
toolbar: {
show: true,
showIcon: false,
},
filterable: true,
paging: true,
pageSize: 3,
data: [
{name: '张三', value: 1, selected: true},
{name: '李四', value: 2, selected: true},
{name: '王五', value: 3},
{name: '赵六', value: 4},
{name: '苹果', value: 5},
{name: '香蕉', value: 6},
{name: '凤梨', value: 7},
{name: '葡萄', value: 8},
{name: '樱桃', value: 9},
{name: '车厘子', value: 10},
{name: '火龙果', value: 11},
]
})
</script>
```
:::
### 自定义工具条
:::demo
```html
<div id="demo3" class="xm-select-demo"></div>
<script>
var demo3 = xmSelect.render({
el: '#demo3',
toolbar: {
show: true,
list: ['ALL', {
name: '自定义',
icon: 'el-icon-star-off',
method(data){
alert('我是自定义的');
}
}]
},
filterable: true,
paging: true,
pageSize: 3,
data: [
{name: '张三', value: 1, selected: true},
{name: '李四', value: 2, selected: true},
{name: '王五', value: 3},
{name: '赵六', value: 4},
{name: '苹果', value: 5},
{name: '香蕉', value: 6},
{name: '凤梨', value: 7},
{name: '葡萄', value: 8},
{name: '樱桃', value: 9},
{name: '车厘子', value: 10},
{name: '火龙果', value: 11},
]
})
</script>
```
:::

99
docs/mds/ZM01.md Normal file
View File

@ -0,0 +1,99 @@
## 赋值与取值
### 给多选赋值
函数`setValue(array)`, 动态赋值多选选中的数据, array可以是value数组, 也可以是完整的object数组
:::demo
```html
<div id="demo1" class="xm-select-demo"></div>
<button class="btn" id="demo1-test1">赋值张三</button>
<button class="btn" id="demo1-test2">赋值张三(value方式)</button>
<button class="btn" id="demo1-test3">清空</button>
<pre id="demo1-result"></pre>
<script>
var demo1 = xmSelect.render({
el: '#demo1',
data: [
{name: '张三', value: 1},
{name: '李四', value: 2},
{name: '王五', value: 3},
]
})
document.getElementById('demo1-test1').onclick = function(){
demo1.setValue([
{name: '张三', value: 1},
])
};
document.getElementById('demo1-test2').onclick = function(){
demo1.setValue([ 1 ])
};
document.getElementById('demo1-test3').onclick = function(){
demo1.setValue([ ])
};
</script>
```
:::
### 取值
函数`getValue(type)`, type类型 name, nameStr, value, valueStr
:::demo
```html
<div id="demo2" class="xm-select-demo"></div>
<button class="btn" id="demo2-getValue">获取选中值</button>
<br/><br/>
<button class="btn" id="name">获取name数组</button>
<button class="btn" id="nameStr">获取name字符串</button>
<button class="btn" id="value">获取value数组</button>
<button class="btn" id="valueStr">获取value字符串</button>
<pre id="demo2-value"></pre>
<script>
var demo2 = xmSelect.render({
el: '#demo2',
data: [
{name: '张三', value: 1},
{name: '李四', value: 2},
{name: '王五', value: 3},
]
})
document.getElementById('demo2-getValue').onclick = function(){
//获取当前多选选中的值
var selectArr = demo2.getValue();
document.getElementById('demo2-value').innerHTML = JSON.stringify(selectArr, null, 2);
}
document.getElementById('name').onclick = function(){
//获取当前多选选中的值
var selectArr = demo2.getValue('name');
document.getElementById('demo2-value').innerHTML = JSON.stringify(selectArr, null, 2);
}
var types = ['name', 'nameStr', 'value', 'valueStr'];
types.forEach(function(type){
document.getElementById(type).onclick = function(){
//获取当前多选选中的值
var selectArr = demo2.getValue(type);
document.getElementById('demo2-value').innerHTML = JSON.stringify(selectArr, null, 2);
}
});
</script>
```
:::

50
docs/mds/ZM02.md Normal file
View File

@ -0,0 +1,50 @@
## 表单提交
### 默认表单提交
:::demo
```html
<form>
<div id="demo1" class="xm-select-demo"></div>
<button class="btn" type="submit">提交</button>
</form>
<script>
var demo1 = xmSelect.render({
el: '#demo1',
data: [
{name: '张三', value: 1},
{name: '李四', value: 2},
{name: '王五', value: 3},
]
})
</script>
```
:::
### 修改name
:::demo
```html
<form>
<div id="demo2" class="xm-select-demo"></div>
<button class="btn" type="submit">提交</button>
</form>
<script>
var demo2 = xmSelect.render({
el: '#demo2',
name: 'lalalalalala',
data: [
{name: '张三', value: 1},
{name: '李四', value: 2},
{name: '王五', value: 3},
]
})
</script>
```
:::

View File

@ -28,7 +28,7 @@
[![star](https://gitee.com/maplemei/xm-select/badge/star.svg?theme=dark)](https://gitee.com/maplemei/xm-select/stargazers) [![star](https://gitee.com/maplemei/xm-select/badge/star.svg?theme=dark)](https://gitee.com/maplemei/xm-select/stargazers)
[![fork](https://gitee.com/maplemei/xm-select/badge/fork.svg?theme=dark)](https://gitee.com/maplemei/xm-select/members) [![fork](https://gitee.com/maplemei/xm-select/badge/fork.svg?theme=dark)](https://gitee.com/maplemei/xm-select/members)
[https://gitee.com/maplemei/xm-select](https://gitee.com/maplemei/xm-select) [码云下载](https://gitee.com/maplemei/xm-select/releases)

View File

@ -32,6 +32,10 @@
| hide | 隐藏下拉的回调 | function | - | - | | hide | 隐藏下拉的回调 | function | - | - |
| template | 自定义渲染选项 | function({ item, sels, name, value }) | - | - | | template | 自定义渲染选项 | function({ item, sels, name, value }) | - | - |
| on | 监听选中变化 | function({ arr, item, selected }) | - | - | | on | 监听选中变化 | function({ arr, item, selected }) | - | - |
| max | 设置多选选中上限 | int | - | 0 |
| maxMethod | 达到选中上限的回到 | function(sels, item), sels: 已选中数据, item: 当前选中的值 | - | - |
| name | 表单提交时的name | string | - | select |
| toolbar | 工具条, 具体看下表 | object | - | - |
### prop ### prop
@ -47,8 +51,9 @@
### theme ### theme
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ----------------- | ------------------------------ | --------------- | ------ | ------ | | ------------ | --------- | --------------- | ------ | ------ |
| color | 颜色 | string | - | #009688 | | color | 主题颜色 | string | - | #009688 |
| maxColor | 选中上限闪烁边框颜色 | string | - | #e54d42 |
### model ### model
@ -90,6 +95,37 @@ model: {
``` ```
### toolbar
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------------ | --------- | --------------- | ------ | ------ |
| show | 是否展示工具条 | boolean | true / false | false |
| showIcon | 是否显示工具图标 | boolean | true / false | true |
| list | 工具条数组 (默认有 全选/清空, 可以自定义) | array | - | [ "ALL", "CLEAR" ] |
- 自定义方式
```
list: [ "ALL", "CLEAR",
{
//显示图标, 可以是layui内置的图标, 也可以是自己引入的图标
//传入的icon会转化为 <i class="layui-icon layui-icon-face-smile"></i>
icon: 'layui-icon layui-icon-face-smile',
//显示名称
name: 'xxx',
//点击时触发的回调
method: function(data){
//data 当前页面的数据
}
}
]
```
### 方法 ### 方法
:::warning :::warning
@ -98,10 +134,10 @@ xmSelect.render()后会返回一个xmSelect对象, 可以进行方法调用
| 事件名 | 说明 | 参数 | | 事件名 | 说明 | 参数 |
| ------ | ------------------ | -------- | | ------ | ------------------ | -------- |
| getValue | 获取当前选中的数据 | - | | getValue | 获取当前选中的数据 | (type: 类型), 可选值: name, nameStr, value, valueStr |
| setValue | 动态设置数据 | array: 选中的数据, show: 是否展开下拉 | | setValue | 动态设置数据 | (array: 选中的数据, show: 是否展开下拉) |
| opened | 主动展开下拉 | - | | opened | 主动展开下拉 | - |
| closed | 主动关闭下拉 | - | | closed | 主动关闭下拉 | - |
| render | 重新渲染多选 | (options):见配置项 | | render | 重新渲染多选 | (options: 见配置项) |
| reset | 重置为上一次的render状态 | - | | reset | 重置为上一次的render状态 | - |
| update | 更新多选选中, reset不保留 | - | | update | 更新多选选中, reset不保留 | - |

View File

@ -1,10 +1,16 @@
## 常见问题 ## 常见问题
### formSelects 与 xm-select
[formSelects](https://github.com/hnzzmsf/layui-formSelects/)是作者很久以前开发的一款多选插件, 在jQuery时代还是相对比较稳定, 不过性能上有很大的问题。痛并思痛后,开始学习其他开源组件的编写方案,最后决定重新开发。
[xm-select](https://gitee.com/maplemei/xm-select)作者精心二次开发的组件, 在formSelects的样式基础上进行了性能优化。目前看来还是比较稳定的 ^_^
### 1.在哪里下载 ### 1.在哪里下载
[Gitee码云下载](https://gitee.com/maplemei/xm-select), 使用时引入`xm-select.js`即可, 具体请看[入门指南](/#/component/install) [Gitee码云下载](https://gitee.com/maplemei/xm-select/releases), 使用时引入`xm-select.js`即可, 已经内置了css, 具体请看[入门指南](/#/component/install)
### 2.为什么多选不显示 ### 2.为什么多选不显示
@ -14,4 +20,5 @@
### 3.渲染后还是不显示 ### 3.渲染后还是不显示
打开控制台查看是否报错, 加群: 660408068, 询问 - 打开控制台查看是否报错
- 加群: 660408068, 询问

View File

@ -63,6 +63,17 @@ export default [{
{ path: '/example/XM15', name: '构建选项', component: importMd('/XM15') }, { path: '/example/XM15', name: '构建选项', component: importMd('/XM15') },
{ path: '/example/XM16', name: '监听选择', component: importMd('/XM16') }, { path: '/example/XM16', name: '监听选择', component: importMd('/XM16') },
{ path: '/example/XM17', name: '性能测试', component: importMd('/XM17') }, { path: '/example/XM17', name: '性能测试', component: importMd('/XM17') },
{ path: '/example/XM18', name: '多选上限', component: importMd('/XM18') },
{ path: '/example/XM19', name: '工具条', component: importMd('/XM19') },
]
}, {
path: '/example-custom',
name: '进阶示例',
redirect: '/example-custom/ZM01',
component: Component,
children: [
{ path: '/example-custom/ZM01', name: '赋值与取值', component: importMd('/ZM01') },
{ path: '/example-custom/ZM02', name: '表单提交', component: importMd('/ZM02') },
] ]
}, { }, {
path: '/question', path: '/question',

View File

@ -1,7 +1,7 @@
{ {
"name": "xm-select", "name": "xm-select",
"version": "1.0.0", "version": "1.0.4",
"description": "", "description": "始于Layui的select多选解决方案",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=dev node_modules/.bin/webpack-dev-server --config build/webpack.config.js", "dev": "cross-env NODE_ENV=dev node_modules/.bin/webpack-dev-server --config build/webpack.config.js",

View File

@ -37,6 +37,57 @@ export function isArray(obj){
return Object.prototype.toString.call(obj) == "[object Array]"; return Object.prototype.toString.call(obj) == "[object Array]";
} }
/**
* 检测对象是否为函数
*/
export function isFunction(obj) {
return Object.prototype.toString.call(obj) == "[object Function]";
}
/**
* 转化为数字
*/
export function toNum(obj) {
obj -= 0;
isNaN(obj) && (obj = 0);
return obj;
}
/**
* 简单的深度合并
*/
export function deepMerge(obj1, obj2) {
let key;
for (key in obj2) {
// 如果target(也就是obj1[key])存在且是对象的话再去调用deepMerge否则就是obj1[key]里面没这个对象需要与obj2[key]合并
// 如果obj2[key]没有值或者值不是对象此时直接替换obj1[key]
obj1[key] =
obj1[key] &&
obj1[key].toString() === "[object Object]" &&
(obj2[key] && obj2[key].toString() === "[object Object]") ?
deepMerge(obj1[key], obj2[key]) :
(obj1[key] = obj2[key]);
}
return obj1;
}
/**
* 数组合并
*/
export function mergeArr(arr1, arr2, prop) {
let value = prop.value;
let result = [...arr2];
for (let i in arr1) {
let item = arr1[i];
if (!arr2.find(a => a[value] == item[value])) {
result.push(item);
}
}
return result;
}
export function watch(data) { export function watch(data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
for (let key in data) { for (let key in data) {
@ -65,3 +116,32 @@ export function checkUserAgent(){
} }
return 'win'; return 'win';
} }
export function IEVersion() {
var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器
var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器
var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
if (isIE) {
var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
reIE.test(userAgent);
var fIEVersion = parseFloat(RegExp["$1"]);
if (fIEVersion == 7) {
return 7;
} else if (fIEVersion == 8) {
return 8;
} else if (fIEVersion == 9) {
return 9;
} else if (fIEVersion == 10) {
return 10;
} else {
return 6; //IE版本<=7
}
} else if (isEdge) {
return 'edge'; //edge
} else if (isIE11) {
return 11; //IE11
} else {
return -1; //不是ie浏览器
}
}

View File

@ -9,6 +9,8 @@ export default function (lan = 'zn') {
return { return {
//多选数据 //多选数据
data: [], data: [],
//表单提交的name
name: 'select',
//默认选中数据, 优先级大于selected //默认选中数据, 优先级大于selected
initValue: null, initValue: null,
//默认提示 //默认提示
@ -48,6 +50,15 @@ export default function (lan = 'zn') {
repeat: false, repeat: false,
//是否点击选项后自动关闭下拉框 //是否点击选项后自动关闭下拉框
clickClose: false, clickClose: false,
//多选上限
max: 0,
maxMethod: function(sels, item){},
//工具条
toolbar: {
show: false,
showIcon: true,
list: [ 'ALL', 'CLEAR' ],
},
//自定义属性名称 //自定义属性名称
prop: { prop: {
name: 'name', name: 'name',
@ -57,7 +68,8 @@ export default function (lan = 'zn') {
}, },
//主题配置 //主题配置
theme: { theme: {
color: '#009688', color: '#009688', //默认主题颜色
maxColor: '#e54d42', //多选上限边框闪烁颜色
}, },
//模型 //模型
model: { model: {

View File

@ -1,6 +1,6 @@
import { h, Component, render } from '@/components/preact' import { h, Component, render } from '@/components/preact'
import Framework from '@/components/element/framework' import Framework from '@/components/element/framework'
import { selector, warn, listenerClose, watch, safety, isArray } from '@/components/common/util' import { selector, warn, listenerClose, watch, safety, isArray, deepMerge } from '@/components/common/util'
import defaultOptions from '@/components/config/options' import defaultOptions from '@/components/config/options'
@ -30,15 +30,15 @@ class xmOptions {
//定义默认值 //定义默认值
this.options = defaultOptions(options.language); this.options = defaultOptions(options.language);
//开始渲染数据 //开始渲染数据
this.update(options, true); this.update(options);
} }
/** /**
* 更新数据 + 重新渲染 * 更新数据 + 重新渲染
*/ */
update(options = {}, isNew){ update(options = {}){
//记录最新的配置项 //记录最新的配置项
this.options = {...this.options, ...options}; this.options = deepMerge(this.options, options);
//如果dom不存在, 则不进行渲染事项 //如果dom不存在, 则不进行渲染事项
let dom = selector(this.options.el); let dom = selector(this.options.el);
@ -46,16 +46,10 @@ class xmOptions {
warn(`没有找到渲染对象: ${options.el}, 请检查`) warn(`没有找到渲染对象: ${options.el}, 请检查`)
return ; return ;
} }
//如果是历史渲染过的数据, 重置一下数据
isNew && childs[this.options.el] && childs[this.options.el].reset();
let isRender = false;
const onRef = (ref) => childs[this.options.el] = ref; const onRef = (ref) => childs[this.options.el] = ref;
const onReset = result => {
this.options.data = result;
}
render(<Framework { ...this.options } onReset={ onReset } onClose={ onClose } onRef={ onRef } />, dom); render(<Framework { ...this.options } onClose={ onClose } onRef={ onRef } />, dom);
//记录数据 //记录数据
data[this.options.el] = this; data[this.options.el] = this;
@ -98,8 +92,23 @@ class xmOptions {
/** /**
* 获取多选选中的数据 * 获取多选选中的数据
*/ */
getValue(){ getValue(type){
return safety(childs[this.options.el].state.sels); let arr = safety(childs[this.options.el].state.sels);
if(type === 'name'){
return arr.map(item => item[this.options.prop.name]);
}else
if(type === 'nameStr'){
return arr.map(item => item[this.options.prop.name]).join(',');
}else
if(type === 'value'){
return arr.map(item => item[this.options.prop.value]);
}else
if(type === 'valueStr'){
return arr.map(item => item[this.options.prop.value]).join(',');
}
return arr;
} }
/** /**

View File

@ -1,5 +1,5 @@
import { h, Component, render } from '@/components/preact' import { h, Component, render } from '@/components/preact'
import { checkUserAgent } from '@/components/common/util' import { checkUserAgent, isFunction, toNum } from '@/components/common/util'
//渲染类 //渲染类
import Tips from './tips'; import Tips from './tips';
@ -14,16 +14,18 @@ class Framework extends Component{
constructor(options){ constructor(options){
super(options); super(options);
//初始化多选数据 //初始化多选数据
this.reset(); this.reset(this.props);
//回传子组件 //回传子组件
this.props.onRef(this); this.props.onRef(this);
this.bodyView = null; this.bodyView = null;
} }
reset(){ reset(props){
let selected = this.props.prop.selected; //用于多选上限的边框颜色变化
this.value(this.props.initValue ? this.props.initValue : this.props.data.filter(item => item[selected]), false); this.updateBorderColor('');
this.resetDate(this.props.data); this.resetDate(props.data);
let selected = props.prop.selected;
this.value(props.initValue ? props.initValue : this.state.data.filter(item => item[selected]), false);
} }
resetDate(data = []){ resetDate(data = []){
@ -31,7 +33,7 @@ class Framework extends Component{
} }
value(sels, show){ value(sels, show){
let data = this.props.data; let data = this.state.data;
let value = this.props.prop.value; let value = this.props.prop.value;
let direction = this.props.direction; let direction = this.props.direction;
this.setState({ this.setState({
@ -44,8 +46,19 @@ class Framework extends Component{
}) })
} }
onReset(data){ updateBorderColor(tmpColor){
this.setState({ tmpColor });
}
onReset(data, type){
//重置数据
if(type === 'data'){
this.resetDate(data); this.resetDate(data);
}else
//重置选中数据
if(type === 'sels'){
this.setState({ sels: data });
}
} }
onClick(e){ onClick(e){
@ -95,12 +108,12 @@ class Framework extends Component{
} }
componentWillReceiveProps(props){ componentWillReceiveProps(props){
this.resetDate(props.data); this.reset(props)
} }
render(config, { sels, show }) { render(config, { sels, show }) {
const { tips, theme, prop, style, radio, repeat, clickClose, on } = config; const { tips, theme, prop, style, radio, repeat, clickClose, on, max, maxMethod } = config;
const borderStyle = { borderColor: theme.color }; const borderStyle = { borderColor: this.state.tmpColor || theme.color };
//最外层边框的属性 //最外层边框的属性
const xmSelectProps = { const xmSelectProps = {
style: { style: {
@ -126,12 +139,23 @@ class Framework extends Component{
//如果是禁用状态, 不能进行操作 //如果是禁用状态, 不能进行操作
if(disabled) return; if(disabled) return;
//查看是否设置了多选上限
let maxCount = toNum(max);
if(maxCount > 0 && sels.length >= maxCount){
this.updateBorderColor(theme.maxColor);
//恢复正常
setTimeout(() => this.updateBorderColor(''), 300);
//查看是否需要回调
maxMethod && isFunction(maxMethod) && maxMethod(sels, item);
return ;
}
//如果现在是选中状态 //如果现在是选中状态
if(selected && (!repeat || mandatoryDelete)){ if(selected && (!repeat || mandatoryDelete)){
let index = sels.findIndex(sel => sel[valueProp] == item[valueProp]) let index = sels.findIndex(sel => sel[valueProp] == item[valueProp])
if(index != -1){ if(index != -1){
sels.splice(index, 1); sels.splice(index, 1);
this.setState(sels); this.setState({ sels });
} }
}else{ }else{
//如果是单选模式 //如果是单选模式
@ -148,6 +172,10 @@ class Framework extends Component{
clickClose && !mandatoryDelete && this.onClick(); clickClose && !mandatoryDelete && this.onClick();
}; };
const select = (
<input class="xm-select-default" name={ config.name } value={ sels.map(item => item[prop.value]).join(',') }></input>
)
const labelProps = { ...config, data: this.state.data, sels, ck, title: sels.map(sel => sel[prop.name]).join(',') } const labelProps = { ...config, data: this.state.data, sels, ck, title: sels.map(sel => sel[prop.name]).join(',') }
const bodyProps = { ...config, data: this.state.data, sels, ck, show, onReset: this.onReset.bind(this) } const bodyProps = { ...config, data: this.state.data, sels, ck, show, onReset: this.onReset.bind(this) }
//控制下拉框的显示于隐藏 //控制下拉框的显示于隐藏
@ -155,6 +183,7 @@ class Framework extends Component{
return ( return (
<xm-select { ...xmSelectProps } > <xm-select { ...xmSelectProps } >
{ select }
<i class={ iconClass } /> <i class={ iconClass } />
<Tips { ...tipsProps } /> <Tips { ...tipsProps } />
<Label { ...labelProps } /> <Label { ...labelProps } />

View File

@ -1,4 +1,5 @@
import { h, Component, render } from '@/components/preact' import { h, Component, render } from '@/components/preact'
import { isFunction, safety, mergeArr, IEVersion } from '@/components/common/util'
/** /**
* 普通的多选渲染 * 普通的多选渲染
@ -53,23 +54,23 @@ class General extends Component{
} }
searchInput(e){ searchInput(e){
let v = e.target.value; let v = e.target.value;
console.log(v, this.state.searchValue)
setTimeout(() => { if(v === this.state.searchValue){
return ;
}
clearTimeout(this.searchCid);
if(this.state.inputOver){ if(this.state.inputOver){
//保证输入框内的值是实时的 //保证输入框内的值是实时的
this.setState({ searchValue: v }); this.setState({ searchValue: v });
//让搜索变成异步的 //让搜索变成异步的
clearTimeout(this.searchCid);
this.searchCid = setTimeout(() => this.setState({ this.searchCid = setTimeout(() => this.setState({
filterValue: this.state.searchValue, filterValue: this.state.searchValue,
remote: true, remote: true,
}), this.props.delay); }), this.props.delay);
} }
}, 0)
} }
focus(){ focus(){
@ -85,8 +86,10 @@ class General extends Component{
if(type === 'compositionstart'){ if(type === 'compositionstart'){
this.setState({ inputOver: false }) this.setState({ inputOver: false })
clearTimeout(this.searchCid);
}else if(type === 'compositionend'){ }else if(type === 'compositionend'){
this.setState({ inputOver: true }) this.setState({ inputOver: true })
this.searchInput(e);
} }
} }
@ -121,7 +124,7 @@ class General extends Component{
this.focus(); this.focus();
this.setState({ loading: false }); this.setState({ loading: false });
this.props.onReset(result); this.props.onReset(result, 'data');
}); });
} }
}else{ }else{
@ -129,12 +132,11 @@ class General extends Component{
} }
} }
const searchClass = ['xm-search', filterable ? '':'dis'].join(' ');
const search = ( const search = (
<div class={ searchClass }> <div class='xm-search'>
<i class="xm-iconfont xm-icon-sousuo"></i> <i class="xm-iconfont xm-icon-sousuo"></i>
<input type="text" class="xm-input xm-search-input" placeholder={ searchTips } value={ this.state.searchValue } <input type="text" class="xm-input xm-search-input" placeholder={ searchTips } value={ this.state.searchValue }
ref={ (input) => { this.searchInputRef = input; } } ref={ input => this.searchInputRef = input }
onClick={ this.blockClick.bind(this) } onClick={ this.blockClick.bind(this) }
onInput={ this.searchInput.bind(this) } onInput={ this.searchInput.bind(this) }
onCompositionStart={ this.handleComposition.bind(this) } onCompositionStart={ this.handleComposition.bind(this) }
@ -177,7 +179,6 @@ class General extends Component{
position: 'relative', position: 'relative',
borderRadius: '1px', borderRadius: '1px',
} }
const pagingClass = 'xm-paging';
// { // {
// ''.padEnd(size, ' ').split('').map((s, i) => ( // ''.padEnd(size, ' ').split('').map((s, i) => (
// <span style={ // <span style={
@ -191,7 +192,7 @@ class General extends Component{
// )) // ))
// } // }
paging = ( paging = (
<div class={ pagingClass }> <div class='xm-paging'>
<span style={ prevStyle } onClick={ this.pagePrevClick.bind(this) }>上一页</span> <span style={ prevStyle } onClick={ this.pagePrevClick.bind(this) }>上一页</span>
<span>{ this.state.pageIndex } / { size }</span> <span>{ this.state.pageIndex } / { size }</span>
<span style={ nextStyle } onClick={ e => this.pageNextClick.bind(this, e, size)() }>下一页</span> <span style={ nextStyle } onClick={ e => this.pageNextClick.bind(this, e, size)() }>下一页</span>
@ -200,6 +201,42 @@ class General extends Component{
} }
let safetyArr = safety(arr);
//工具条操作
const toolbar = (
<div class='xm-toolbar'>
{ config.toolbar.list.map(tool => {
const toolClass = 'toolbar-tag';
let info;
if(tool === 'ALL'){
info = { icon: 'xm-iconfont xm-icon-quanxuan', name: '全选', method: (data) => {
this.props.onReset(mergeArr(data, sels, prop), 'sels');
} };
}else if(tool === 'CLEAR'){
info = { icon: 'xm-iconfont xm-icon-qingkong', name: '清空', method: (data) => {
this.props.onReset([], 'sels');
} };
}else {
info = tool
}
const hoverChange = e => {
if(e.type === 'mouseenter') e.target.style.color = theme.color;
if(e.type === 'mouseleave') e.target.style.color = '';
}
return (<div class={ toolClass } onClick={ () => {
isFunction(info.method) && info.method(safetyArr)
} } onMouseEnter={ hoverChange } onMouseLeave={ hoverChange }>
{ config.toolbar.showIcon && <i class={ info.icon }></i> }
<span>{ info.name }</span>
</div>)
}).filter(a => a) }
</div>
)
arr = arr.map(item => { arr = arr.map(item => {
const selected = !!sels.find(sel => sel[value] == item[value]) const selected = !!sels.find(sel => sel[value] == item[value])
@ -230,8 +267,9 @@ class General extends Component{
return ( return (
<div onClick={ this.blockClick }> <div onClick={ this.blockClick }>
<div> <div>
{ search } { config.toolbar.show && toolbar }
<div style={ {maxHeight: config.height, overflow: 'auto'} }>{ arr }</div> { filterable && search }
<div class="scroll-body" style={ {maxHeight: config.height} }>{ arr }</div>
{ config.paging && paging } { config.paging && paging }
</div> </div>
{ this.state.loading && <div class="loading" > { this.state.loading && <div class="loading" >

View File

@ -41,7 +41,7 @@ xm-select{
box-sizing: border-box; box-sizing: border-box;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
color: @fontColor; // color: @fontColor;
text-overflow: ellipsis; text-overflow: ellipsis;
user-select: none; user-select: none;
-ms-user-select: none; -ms-user-select: none;
@ -168,6 +168,21 @@ xm-select{
animation-duration: .3s; animation-duration: .3s;
animation-fill-mode: both; animation-fill-mode: both;
.scroll-body{
overflow: auto;
.scrollBorder() {
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
-ms-border-radius: 2em;
border-radius:2em;
}
&::-webkit-scrollbar{ width: 8px; }
&::-webkit-scrollbar-track{ .scrollBorder(); background-color: #FFF; }
&::-webkit-scrollbar-thumb{ .scrollBorder(); background-color: #C2C2C2; }
}
&.up{ &.up{
top: auto; top: auto;
bottom: 42px; bottom: 42px;
@ -205,7 +220,7 @@ xm-select{
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
color: #666; color: @fontColor;
width: calc(100% - 20px); width: calc(100% - 20px);
} }
} }
@ -241,6 +256,7 @@ xm-select{
&>i{ &>i{
position: absolute; position: absolute;
color: @fontColor;
} }
&-input{ &-input{
@ -278,6 +294,37 @@ xm-select{
border: 1px solid #e2e2e2; border: 1px solid #e2e2e2;
} }
} }
.xm-toolbar{
padding: 0 10px;
display: flex;
margin: -3px 0;
cursor: default;
.toolbar-tag{
cursor: pointer;
display: flex;
margin-right: 20px;
color: @fontColor;
align-items: baseline;
&:hover{
opacity: .8;
}
&:active{
opacity: 1;
}
&>i{
margin-right: 2px;
font-size: 14px;
}
&:last-child{
margin-right: 0;
}
}
}
} }
.xm-input{ .xm-input{
@ -328,6 +375,10 @@ xm-select{
} }
} }
.xm-select-default{
display: none !important;
}
} }
// xm-select[ua='win']{ // xm-select[ua='win']{