v1.1.0.Beta+

This commit is contained in:
maplemei 2019-11-22 20:33:17 +08:00
parent db6fdae5cd
commit b14cbaa913
17 changed files with 366 additions and 160 deletions

View File

@ -7,17 +7,21 @@
#### 新增
- label也可以自定义渲染
- label新增title提示
- 新增远程分页配置`pageRemote`
#### Bug fixes
- 树形组件
- [新增]`strict`严格父子结构
- [新增]`lazy`懒加载模式
- [修改]树状结构使用`setValue`数据错误
- [修改]树状结构中`children`属性为空数组时无法操作节点的问题
- [修改]半选状态下如无可选子项则变更操作为取消
- 修改`initValue`失效的问题
- 修改`getValue()`方法无法序列化的问题
- 调整拓展中心下拉日期多选的样式
- 修改搜索模式下输入中文的bug
### 1.1.0.Beta

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

2
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

View File

@ -174,3 +174,105 @@ var demo4 = xmSelect.render({
</script>
```
:::
### 远程分页
```
xmSelect.render({
//...
//开启分页
paging: true,
//远程分页
pageRemote: true,
//实现方法
remoteMethod: function(val, cb, show, pageIndex),
})
```
:::demo `render`后, 就会进行一次回调, 用于渲染第一次数据
```html
<div id="demo5" class="xm-select-demo"></div>
<script>
var demo5 = xmSelect.render({
el: '#demo5',
paging: true,
pageRemote: true,
remoteMethod: function(val, cb, show, pageIndex){
//val: 搜索框的内容, 不开启搜索默认为空, cb: 回调函数, show: 当前下拉框是否展开, pageIndex: 当前第几页
//这里的axios类似于ajax
axios({
method: 'get',
url: 'https://www.fastmock.site/mock/98228b1f16b7e5112d6c0c87921eabc1/xmSelect/search',
params: {
keyword: val + '_' + pageIndex,
}
}).then(response => {
//这里是success的处理
var res = response.data;
//回调需要两个参数, 第一个: 数据数组, 第二个: 总页码
cb(res.data, 10)
}).catch(err => {
//这里是error的处理
cb([], 0);
});
}
})
</script>
```
:::
### 搜索 + 远程分页
```
xmSelect.render({
//...
//开启分页
paging: true,
//远程分页
pageRemote: true,
//实现方法
remoteMethod: function(val, cb, show, pageIndex),
})
```
:::demo `render`后, 就会进行一次回调, 用于渲染第一次数据
```html
<div id="demo6" class="xm-select-demo"></div>
<script>
var demo6 = xmSelect.render({
el: '#demo6',
//配置搜索
filterable: true,
//配置远程分页
paging: true,
pageRemote: true,
//数据处理
remoteMethod: function(val, cb, show, pageIndex){
//val: 搜索框的内容, 不开启搜索默认为空, cb: 回调函数, show: 当前下拉框是否展开, pageIndex: 当前第几页
//这里的axios类似于ajax
axios({
method: 'get',
url: 'https://www.fastmock.site/mock/98228b1f16b7e5112d6c0c87921eabc1/xmSelect/search',
params: {
keyword: val + '_' + pageIndex,
}
}).then(response => {
//这里是success的处理
var res = response.data;
//回调需要两个参数, 第一个: 数据数组, 第二个: 总页码
cb(res.data, 10)
}).catch(err => {
//这里是error的处理
cb([], 0);
});
}
})
</script>
```
:::

View File

@ -1,4 +1,4 @@
## 远程搜索
## 弹框中的多选
### layer弹出框

View File

@ -102,3 +102,76 @@ var demo1 = xmSelect.render({
</script>
```
:::
### 懒加载的树
```
tree: {
show: true,
expandedKeys: [ -1 ],
//开启懒加载
lazy: true,
//加载方法
load: function(item, cb){
//item: 点击的节点, cb: 回调函数
//这里模拟ajax
setTimeout(function(){
var name = item.name + 1;
cb([
{name: item.name + 1, value: item.value + '1', children: [] },
{name: item.name + 2, value: item.value + '2' },
])
}, 500)
}
},
```
:::demo
```html
<div id="demo2" class="xm-select-demo"></div>
<script>
var demo2 = xmSelect.render({
el: '#demo2',
autoRow: true,
tree: {
show: true,
showFolderIcon: true,
showLine: true,
indent: 20,
expandedKeys: [ -1 ],
lazy: true,
load: function(item, cb){
setTimeout(function(){
cb([
{name: item.name + 1, value: item.value + '1', children: [] },
{name: item.name + 2, value: item.value + '2' },
])
}, 500)
}
},
height: 'auto',
data(){
return [
{name: '销售员', value: -1, children: [
{name: '张三1', value: 100, selected: true, children: []},
{name: '李四1', value: 2, selected: true},
{name: '王五1', value: 3, disabled: true},
]},
{name: '奖品', value: -2, children: [
{name: '奖品3', value: -3, children: [
{name: '苹果3', value: 14, selected: true},
{name: '香蕉3', value: 15},
{name: '葡萄3', value: 16},
]},
{name: '苹果2', value: 4, selected: true, disabled: true},
{name: '香蕉2', value: 5},
{name: '葡萄2', value: 6},
]},
]
}
})
</script>
```
:::

View File

@ -1,34 +1,41 @@
## 测试
:::demo
```html
<div id="demo1" class="xm-select-demo"></div>
<script>
//先渲染多选
var demo1 = xmSelect.render({
el: '#demo1',
autoRow: true,
tree: {
show: true,
expandedKeys: [-3],
showFolderIcon: true,
showLine: true,
indent: 20,
expandedKeys: [ -1 ],
lazy: true,
load: function(item, cb){
setTimeout(function(){
var name = item.name + 1;
cb([
{name: item.name + 1, value: item.value + '1', children: [] },
{name: item.name + 2, value: item.value + '2' },
])
}, 500)
}
},
height: 'auto',
on({ arr, change, isAdd }){
console.log(arr, change, isAdd)
},
data(){
return [
{name: '销售员', value: -1, disabled: true, children: [
{name: '张三1', value: 1, selected: true},
{name: '销售员', value: -1, children: [
{name: '张三1', value: 100, selected: true, children: []},
{name: '李四1', value: 2, selected: true},
{name: '王五1', value: 3, disabled: true},
]},
{name: '奖品', value: -2, children: [
{name: '奖品3', value: -3, children: [
{name: '苹果3', value: 14, selected: true, disabled: true},
{name: '苹果3', value: 14, selected: true},
{name: '香蕉3', value: 15},
{name: '葡萄3', value: 16},
]},

View File

@ -18,13 +18,14 @@
| filterMethod | 搜索回调函数 | function(val, item, index, prop) val: 当前搜索值, item: 每个option选项, index: 位置数据中的下标, prop: 定义key | - | - |
| filterDone | 搜索完成函数 | function(val, list) val: 当前搜索值, list: 过滤后的数据 | - | - |
| remoteSearch | 是否开启自定义搜索 (远程搜索)| boolean | true / false | false |
| remoteMethod | 自定义搜索回调函数 | function(val, cb, show) val: 当前搜索值, cb: 回调函数, 需要回调一个数组, 结构同data, show: 下拉框显示状态 | - | - |
| remoteMethod | 自定义搜索回调函数 | function(val, cb, show, pageIndex) val: 当前搜索值, cb(arr, totalPage): 回调函数, 需要回调一个数组, 结构同data, 远程分页需要第二个参数: 总页码, show: 下拉框显示状态, pageIndex: 分页下当前页码 | - | - |
| direction | 下拉方向| string | auto / up / down | auto |
| style | 自定义样式| object | - | { } |
| height | 默认最大高度| string | - | 200px |
| paging | 是否开启自定义分页 | boolean | true / false | false |
| pageSize | 分页条数 | int | - | 10 |
| pageEmptyShow | 分页无数据是否显示 | boolean | true / false | true |
| pageRemote | 是否开启远程分页 | boolean | true / false | true |
| radio | 是否开启单选模式 | boolean | true / false | false |
| repeat | 是否开启重复性模式 | boolean | true / false | false |
| clickClose | 是否点击选项后自动关闭下拉框 | boolean | true / false | false |

View File

@ -113,8 +113,8 @@ export default [{
}, {
path: '/test',
name: '测试',
hidden: true,
// hidden: false,
// hidden: true,
hidden: false,
component: importMd('/ZTEST'),
},

View File

@ -33,13 +33,13 @@ class Framework extends Component{
}
init(props, refresh){
let { data } = props, sels;
let { data, prop, initValue } = props, sels;
//如果新数据和旧数据不同 或者 强制刷新 才进行数据处理
if(refresh){
let dataObj = {};
let flatData = [];
this.load(data, dataObj, flatData);
sels = props.initValue ? this.exchangeValue(props.initValue, true, dataObj) : Object.values(dataObj).filter(item => item[props.prop.selected] === true).filter(item => item[this.props.prop.optgroup] !== true)
sels = initValue ? this.exchangeValue(initValue, true, dataObj) : Object.values(dataObj).filter(item => item[prop.selected] === true).filter(item => item[prop.optgroup] !== true)
this.setState({ sels, dataObj, flatData });
}
@ -85,7 +85,8 @@ class Framework extends Component{
const { children, optgroup, value, selected, disabled } = prop;
data.forEach(item => {
//数据提取/处理
item.__node = { parent }
item.__node = { parent, loading: item.__node && item.__node.loading }
dataObj[item[value]] = item;
flatData.push(item);
//遍历子级数据
@ -212,20 +213,19 @@ class Framework extends Component{
}
this.resetSelectValue(sels, [item], !itemSelected);
}
}
let parent = item.__node.parent;
if(parent){
while(parent){
let child = parent[children], len = child.length;
let slen = child.filter(i => sels.findIndex(sel => sel[value] === i[value]) !== -1 || i.__node.selected === true).length;
parent.__node.selected = slen === len;
parent.__node.half = slen > 0 && slen < len;
parent.__node.half = (slen > 0 && slen < len) || child.filter(i => i.__node.half === true).length > 0;
parent = parent.__node.parent;
}
this.setState({ data: this.state.data })
}
}
//检查是否为选择即关闭状态, 强制删除情况下不做处理
clickClose && !mandatoryDelete && this.onClick();
@ -265,8 +265,7 @@ class Framework extends Component{
let changeData = data.filter(item => item[this.props.prop.selected] === true);
this.resetSelectValue(mergeArr(changeData, this.state.sels, this.props.prop), changeData, true);
let dataObj = {};
let flatData = [];
let dataObj = {}, flatData = [];
this.load(data, dataObj, flatData);
this.setState({ data, flatData });
}else
@ -285,6 +284,10 @@ class Framework extends Component{
//自动判断模式
if(type === 'auto'){
this.auto(data);
}else
//树状结构数据更新
if(type === 'treeData'){
this.value(this.state.sels, null, true)
}
}
@ -323,7 +326,6 @@ class Framework extends Component{
}
render(config, state) {
const { theme, prop, radio, repeat, clickClose, on, max, maxMethod, content, disabled, tree } = config;
const borderStyle = { borderColor: theme.color };
let { data, dataObj, flatData, sels, show, tmpColor } = state;
@ -356,7 +358,6 @@ class Framework extends Component{
//渲染组件
let Body = content ? <Custom { ...bodyProps } /> : tree.show ? <Tree { ...bodyProps } /> : <General { ...bodyProps } />;
return (
<xm-select { ...xmSelectProps } >
<input class="xm-select-default" name={ config.name } value={ sels.map(item => item[prop.value]).join(',') }></input>

View File

@ -52,8 +52,9 @@ class Label extends Component{
const conf = label[type];
//渲染结果
let html = '';
let innerHTML = true;
let html = '', innerHTML = true;
//悬浮显示已选择
let title = sels.map(item => item[name]).join(',')
if(type === 'text'){
html = sels.map(sel => `${conf.left}${sel[name]}${conf.right}`).join(conf.separator)
@ -103,7 +104,7 @@ class Label extends Component{
<div class="scroll" ref={ ref => this.labelRef = ref }>
{ innerHTML ?
<div class="label-content" dangerouslySetInnerHTML={{__html: html}}></div> :
<div class="label-content">{ html }</div>
<div class="label-content" title={ title }>{ html }</div>
}
</div>
</div>

View File

@ -14,7 +14,7 @@ class General extends Component{
remote: true,
loading: false,
pageIndex: 1,
pageSize: 10,
totalSize: 0,
});
this.searchCid = 0;
@ -56,6 +56,7 @@ class General extends Component{
return ;
}
this.changePageIndex(index - 1);
this.props.pageRemote && this.postData(index - 1, true);
}
pageNextClick(e, size){
let index = this.state.pageIndex;
@ -63,6 +64,7 @@ class General extends Component{
return ;
}
this.changePageIndex(index + 1);
this.props.pageRemote && this.postData(index + 1, true);
}
changePageIndex(index){
@ -111,6 +113,24 @@ class General extends Component{
}
}
postData(pageIndex = this.state.pageIndex, mandatory = false){
if(this.state.remote || mandatory){
this.callback = false;
this.setState({ loading: true, remote: false });
//让输入框失去焦点
this.blur();
this.props.remoteMethod(this.state.filterValue, (result, totalSize) => {
//回调后可以重新聚焦
this.focus();
this.callback = true;
this.setState({ loading: false, totalSize });
this.props.onReset(result, 'data');
}, this.props.show, pageIndex);
}
}
//组件将要接收新属性
componentWillReceiveProps(props){
if(this.props.show != props.show){
if(!props.show){
@ -125,20 +145,8 @@ class General extends Component{
}
}
componentDidUpdate(){
if(this.callback){
this.callback = false;
let done = this.props.filterDone;
if(isFunction(done)){
done(this.state.filterValue, this.tempData || []);
}
}
}
render(config) {
let { data, flatData, prop, template, theme, radio, sels, empty, filterable, filterMethod, remoteSearch, remoteMethod, delay, searchTips, create } = config
let { data, flatData, prop, template, theme, radio, sels, empty, filterable, filterMethod, remoteSearch, remoteMethod, delay, searchTips, create, pageRemote } = config
const { name, value, disabled, children, optgroup } = prop;
@ -146,20 +154,7 @@ class General extends Component{
//是否开启了搜索
if(filterable){
if(remoteSearch){//是否进行远程搜索
if(this.state.remote){
this.callback = false;
this.setState({ loading: true, remote: false });
//让输入框失去焦点
this.blur();
remoteMethod(this.state.filterValue, result => {
//回调后可以重新聚焦
this.focus();
this.callback = true;
this.setState({ loading: false });
this.props.onReset(result, 'data');
}, this.props.show);
}
this.postData();
}else{
const filterData = (item, index) => {
const isGroup = item[optgroup];
@ -187,17 +182,15 @@ class General extends Component{
}
}
//远程分页
if(pageRemote){
this.postData();
}
const search = (
<div class='xm-search'>
<i class="xm-iconfont xm-icon-sousuo"></i>
<input type="text" class="xm-input xm-search-input" placeholder={ searchTips }
ref={ input => this.searchInputRef = input }
onClick={ this.blockClick.bind(this) }
onInput={ this.searchInput.bind(this) }
onCompositionStart={ this.handleComposition.bind(this) }
onCompositionUpdate={ this.handleComposition.bind(this) }
onCompositionEnd={ this.handleComposition.bind(this) }
/>
<input class="xm-input xm-search-input" placeholder={ searchTips } />
</div>
);
@ -211,7 +204,7 @@ class General extends Component{
let paging = '';
if(config.paging){
//计算当前分页的总页码
let size = Math.floor((arr.length - 1) / config.pageSize) + 1;
let size = pageRemote ? this.state.totalSize : Math.floor((arr.length - 1) / config.pageSize) + 1;
size <= 0 && (size = 1);
let pageIndex = this.state.pageIndex;
@ -226,10 +219,12 @@ class General extends Component{
pageIndex = 1;
}
if(!pageRemote){
//实现简单的物理分页
let start = (pageIndex - 1) * config.pageSize;
let end = start + config.pageSize;
arr = arr.slice(start, end);
}
const disabledStyle = {cursor: 'no-drop', color: '#d2d2d2'};
@ -237,23 +232,6 @@ class General extends Component{
pageIndex <= 1 && (prevStyle = disabledStyle);
pageIndex == size && (nextStyle = disabledStyle);
// const defaultCurrClass = {
// position: 'relative',
// borderRadius: '1px',
// }
// {
// ''.padEnd(size, ' ').split('').map((s, i) => (
// <span style={
// this.state.pageIndex == i + 1 ? {
// ...defaultCurrClass,
// backgroundColor: theme.color,
// borderColor: theme.color,
// color: '#FFF',
// } : defaultCurrClass
// }>{ i + 1 }</span>
// ))
// }
this.state.pageIndex !== pageIndex && this.changePageIndex(pageIndex);
paging = (
@ -324,11 +302,6 @@ class General extends Component{
})
this.props.onReset(mergeArr(list, selectedList, prop), 'sels');
} };
}else if(tool === 'SEARCH'){
toolStyle.color = theme.color;
info = { icon: 'xm-iconfont xm-icon-sousuo', name, method: (pageData) => {
} };
}else {
info = tool
}
@ -407,6 +380,31 @@ class General extends Component{
</div>
)
}
//组件完成挂载
componentDidMount(){
let input = this.base.querySelector('.xm-search-input');
if(input){
input.addEventListener('compositionstart', this.handleComposition.bind(this));
input.addEventListener('compositionupdate', this.handleComposition.bind(this));
input.addEventListener('compositionend', this.handleComposition.bind(this));
input.addEventListener('input', this.searchInput.bind(this));
this.searchInputRef = input;
}
}
//此时页面又被重新渲染了
componentDidUpdate(){
if(this.callback){
this.callback = false;
let done = this.props.filterDone;
if(isFunction(done)){
done(this.state.filterValue, this.tempData || []);
}
}
}
}
export default General;

View File

@ -34,11 +34,28 @@ class Tree extends Component{
optionClick(item, selected, disabled, type, e){
if(type === 'line'){
if(item.__node.loading === true){
return;
}
let val = item[this.props.prop.value];
let expandedKeys = this.state.expandedKeys;
let index = expandedKeys.findIndex(v => v === val);
index === -1 ? expandedKeys.push(val) : expandedKeys.splice(index, 1);
this.setState({ expandedKeys });
//是否需要懒加载
const { tree, prop, sels } = this.props;
let child = item[prop.children];
if(tree.lazy && child && child.length === 0 && item.__node.loading !== false){
item.__node.loading = true;
tree.load(item, (result) => {
item.__node.loading = false;
item[prop.children] = result;
item[prop.selected] = sels.findIndex(i => i[prop.value] === item[prop.value]) != -1
this.props.onReset(item, 'treeData');
});
}
}else if(type === 'checkbox'){
this.props.ck(item, selected, disabled);
}
@ -88,12 +105,14 @@ class Tree extends Component{
}
const className = ['xm-option', (dis ? ' disabled' : ''), (selected ? ' selected' : ''), (showIcon ? 'show-icon' : 'hide-icon') ].join(' ');
const iconClass = ['xm-option-icon xm-iconfont', radio ? 'xm-icon-danx' : tree.strict && half ? 'xm-icon-banxuan' : 'xm-icon-duox'].join(' ');
const treeIconClass = ['xm-tree-icon', expand ? 'expand':'', item[children] && item[children].length > 0 ? 'visible':'hidden'].join(' ');
const treeIconClass = ['xm-tree-icon', expand ? 'expand':'', item[children] && (item[children].length > 0 || tree.lazy) ? 'visible':'hidden'].join(' ');
return (
<div class={ className } style={ itemStyle } value={ item[value] } onClick={ this.optionClick.bind(this, item, selected, item[disabled], 'line') }>
{ tree.showFolderIcon && <i class={ treeIconClass }></i> }
{ tree.showFolderIcon && tree.showLine && <i class={ expand ? 'top-line expand' : 'top-line' } style={ { left: indent - tree.indent + 3 + 'px', width: tree.indent + (expand === 0 ? 10 : -2) + 'px' } }></i> }
{ tree.showFolderIcon && tree.showLine && <i class={ expand ? 'left-line expand' : 'left-line' } style={ {left: indent - tree.indent + 3 + 'px'} }></i> }
{ item.__node.loading && <span class="loader"></span> }
{ showIcon && <i class={ iconClass } style={ iconStyle } onClick={ this.optionClick.bind(this, item, selected, item[disabled], 'checkbox') }></i> }
<div class='xm-option-content' dangerouslySetInnerHTML={{ __html: template({ data, item, arr: sels, name: item[name], value: item[value] }) }}></div>
</div>
@ -103,11 +122,12 @@ class Tree extends Component{
const renderGroup = (item, indent) => {
const child = item[children];
indent = indent + tree.indent
if(child && child.length > 0){//分组模式
if(child){//分组模式
let expand = this.state.expandedKeys.findIndex(k => item[value] === k) !== -1;
child.length === 0 && (expand = false)
return (
<div class="xm-tree">
{ tree.showFolderIcon && tree.showLine && <i class={ expand ? 'left-line expand' : 'left-line' } style={ {left: indent + 3 + 'px'} }></i> }
{ tree.showFolderIcon && tree.showLine && expand && <i class='left-line left-line-group' style={ {left: indent + 3 + 'px'} }></i> }
{ renderItem(item, indent, expand) }
{ expand && <div class="xm-tree-box">{ child.map(c => renderGroup(c, indent)) }</div> }
</div>

View File

@ -60,6 +60,8 @@ export default function (lan = 'zn') {
pageSize: 10,
//分页无数据是否展示分页
pageEmptyShow: true,
//是否开启远程分页
pageRemote: false,
//是否开启单选模式
radio: false,
//是否开启重复选模式
@ -91,6 +93,10 @@ export default function (lan = 'zn') {
expandedKeys: [],
//是否严格遵守父子模式
strict: true,
//是否懒加载
lazy: false,
//懒加载回调
load: null,
},
//自定义属性名称
prop: {

View File

@ -243,6 +243,13 @@ xm-select{
&.selected.hide-icon .xm-option-content{
color: #FFF !important;
}
.loader{
width: 0.8em;
height: 0.8em;
margin-right: 6px;
color: #C2C2C2;
}
}
.xm-select-empty{
@ -392,10 +399,8 @@ xm-select{
z-index: 1;
border-top: 1px dotted #c0c4cc !important;
}
&-box{
&.dis{
}
.xm-tree-icon+.top-line{
margin-left: 1px;
}
}
.scroll-body>.xm-tree>.xm-option>.top-line{
@ -434,8 +439,7 @@ xm-select{
display: flex;
align-items: center;
justify-content: center;
}
.loader{
border: .2em dotted currentcolor;
@ -450,7 +454,6 @@ xm-select{
vertical-align: middle;
pointer-events: none;
}
}
.xm-select-default{
display: none !important;
@ -502,25 +505,15 @@ xm-select{
}
.xm-tree{
.left-line{
height: calc(100% - @size);
top: @size / 2;
bottom: @size / 2;
&.expand{
height: 100%;
bottom: 0;
bottom: @size / 2;
}
}
&:last-child{
.left-line{
.left-line-group{
height: calc(100% - @size);
}
}
.xm-tree-icon.hidden+.top-line{
top: @size / 2 - 1px;
}
.xm-tree-icon+.top-line{
margin-left: 1px;
}
}
}
xm-select[size='large']{