This commit is contained in:
maplemei 2020-02-10 14:39:38 +08:00
parent 588abc3d5d
commit 27f86e678c
16 changed files with 253 additions and 90 deletions

View File

@ -1,5 +1,20 @@
## 更新日志 ## 更新日志
### 1.1.8
*2020-02-10*
#### 新增
- 新增级联模式(第一版, 欢迎测试Bug)
#### Bug fixes
- 修改class .hidden为.xm-hidden 避免冲突
- 修改tree模式下只有一个子节点是的虚线样式错误
- 修改tree非严格模式下的工具条操作数据错误
### 1.1.7 ### 1.1.7
*2020-01-02* *2020-01-02*

4
dist/static/2.js vendored

File diff suppressed because one or more lines are too long

4
dist/static/3.js vendored

File diff suppressed because one or more lines are too long

4
dist/static/docs.js vendored

File diff suppressed because one or more lines are too long

4
dist/xm-select.js vendored

File diff suppressed because one or more lines are too long

93
docs/mds/ZP06.md Normal file
View File

@ -0,0 +1,93 @@
## 级联模式
### cascader
默认配置
```
cascader: {
//是否显示树状结构
show: false,
//间距
indent: 20,
//是否严格遵守父子模式
strict: true,
},
```
:::demo
```html
<div id="demo1" class="xm-select-demo"></div>
<br/>
<div class="layui-form">
<input type="checkbox" name="strict" lay-filter="strict" lay-skin="primary" title="严格父子结构" checked>
</div>
<div style="margin-top: 20px">间距</div>
<div id="slideTest1"></div>
<script>
layui.form.render();
['strict'].forEach(function(key){
layui.form.on('checkbox('+key+')', function(data){
var config = {};
config[key] = data.elem.checked;
demo1.update({
cascader: config
})
});
})
layui.slider.render({
elem: '#slideTest1',
min: 50,
max: 300,
showstep: true,
input: true,
tips: true,
value: 100,
change: function(value){
demo1.update({
cascader: {
indent: value
}
})
}
});
var demo1 = xmSelect.render({
el: '#demo1',
autoRow: true,
cascader: {
show: true,
indent: 100,
},
height: '200px',
data(){
return [
{name: '销售员', value: -1, disabled: true, children: [
{name: '张三1', value: 1, 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

@ -7,43 +7,27 @@
<script> <script>
var demo1 = xmSelect.render({ var demo1 = xmSelect.render({
el: '#demo1', el: '#demo1',
tree: { autoRow: true,
show: true,
showFolderIcon: true,
showLine: true,
indent: 20,
expandedKeys: true,
},
filterable: true, filterable: true,
filterDone(){ cascader: {
console.log('搜索结束') show: true,
}, indent: 100,
remoteSearch: true,
//远程搜索回调
remoteMethod: function(val, cb){
console.log('远程')
cb([
{name: '销售员', value: -1, disabled: true, children: [
{name: '张三张三张三张三张三张三张三张三张三张三张三张三张三张三张三张三1', value: 1, selected: true, children: []},
{name: '李四1', value: 2, selected: true},
{name: '王五1', value: 3, disabled: true},
]},
]);
}, },
height: '100px',
toolbar: { toolbar: {
show: true, show: true,
list: ['ALL', 'REVERSE', 'CLEAR'] list: ['ALL', 'REVERSE', 'CLEAR']
}, },
height: 'auto', filterable: true,
data(){ data(){
return [ return [
{name: '销售员', value: -1, disabled: true, children: [ {name: '销售员', value: -1, disabled: true, children: [
{name: '张三张三张三张三张三张三张三张三张三张三张三张三张三张三张三张三1', value: 1, selected: true, children: []}, {name: '张三11111111111', value: 1, selected: true, children: []},
{name: '李四1', value: 2, selected: true}, {name: '李四1', value: 2, selected: true},
{name: '王五1', value: 3, disabled: true}, {name: '王五1', value: 3, disabled: true},
]}, ]},
{name: '奖品', value: -2, children: [ {name: '奖品', value: -2, children: [
{name: '奖品3', value: -3, children: [ {name: '奖品3333333333', value: -3, children: [
{name: '苹果3', value: 14, selected: true}, {name: '苹果3', value: 14, selected: true},
{name: '香蕉3', value: 15}, {name: '香蕉3', value: 15},
{name: '葡萄3', value: 16}, {name: '葡萄3', value: 16},
@ -56,7 +40,6 @@ var demo1 = xmSelect.render({
} }
}) })
</script> </script>
``` ```
::: :::

View File

@ -48,6 +48,7 @@
| disabled | 是否禁用多选 | boolean | true / false | false | | disabled | 是否禁用多选 | boolean | true / false | false |
| create | 创建条目 | function(val, data), val: 搜索的数据, data: 当前下拉数据 | - | null | | create | 创建条目 | function(val, data), val: 搜索的数据, data: 当前下拉数据 | - | null |
| tree | 树形结构, 具体看下表 | object | - | - | | tree | 树形结构, 具体看下表 | object | - | - |
| cascader | 级联结构, 具体看下表 | object | - | - |
### prop ### prop
@ -173,6 +174,15 @@ list: [ "ALL", "CLEAR",
| indent | 间距 | int | - | 20 | | indent | 间距 | int | - | 20 |
| expandedKeys | 默认展开的节点数组, 为true时展开所有节点 | array / boolean | - | [ ] | | expandedKeys | 默认展开的节点数组, 为true时展开所有节点 | array / boolean | - | [ ] |
| strict | 是否遵循严格父子结构 | boolean | true / false | true | | strict | 是否遵循严格父子结构 | boolean | true / false | true |
### cascader
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ----------------- | --------------------- | ------------- | ------------- | ------ |
| show | 是否展示为级联结构 | boolean | true / false | false |
| indent | 每一级的宽度 | int | - | 100 |
| strict | 是否遵循严格父子结构 | boolean | true / false | true |
### 全局方法 ### 全局方法

View File

@ -101,6 +101,7 @@ export default [{
{ path: '/plugin/laydate', name: '下拉日期多选', component: importMd('/ZP03') }, { path: '/plugin/laydate', name: '下拉日期多选', component: importMd('/ZP03') },
{ path: '/plugin/panel', name: '下拉折叠面板', component: importMd('/ZP04') }, { path: '/plugin/panel', name: '下拉折叠面板', component: importMd('/ZP04') },
{ path: '/plugin/transfer', name: '下拉穿梭框', component: importMd('/ZP05') }, { path: '/plugin/transfer', name: '下拉穿梭框', component: importMd('/ZP05') },
{ path: '/plugin/cascader', name: '级联模式 Cascader', component: importMd('/ZP06') },
] ]
}, { }, {
path: '/question', path: '/question',

View File

@ -1,6 +1,6 @@
{ {
"name": "xm-select", "name": "xm-select",
"version": "1.1.7", "version": "1.1.8",
"description": "始于Layui的select多选解决方案", "description": "始于Layui的select多选解决方案",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -6,7 +6,7 @@ import Label from '../label';
import General from '../plugin/general'; import General from '../plugin/general';
import Custom from '../plugin/custom'; import Custom from '../plugin/custom';
import Tree from '../plugin/tree'; import Tree from '../plugin/tree';
// import Cascader from '../plugin/cascader'; import Cascader from '../plugin/cascader';
/** /**
* 框架渲染类, 渲染基础的外边框 + 属性变化监听 * 框架渲染类, 渲染基础的外边框 + 属性变化监听
@ -384,8 +384,7 @@ class Framework extends Component{
const bodyProps = { ...config, data, dataObj, flatData, sels, ck: this.itemClick.bind(this), show, onReset: this.onReset.bind(this) } const bodyProps = { ...config, data, dataObj, flatData, sels, ck: this.itemClick.bind(this), show, onReset: this.onReset.bind(this) }
//渲染组件 //渲染组件
// let Body = content ? <Custom { ...bodyProps } /> : tree.show ? <Tree { ...bodyProps } /> : config.cascader.show ? <Cascader { ...bodyProps } /> : <General { ...bodyProps } />; let Body = content ? <Custom { ...bodyProps } /> : tree.show ? <Tree { ...bodyProps } /> : config.cascader.show ? <Cascader { ...bodyProps } /> : <General { ...bodyProps } />;
let Body = content ? <Custom { ...bodyProps } /> : tree.show ? <Tree { ...bodyProps } /> : <General { ...bodyProps } />;
return ( return (
<xm-select { ...xmSelectProps } > <xm-select { ...xmSelectProps } >

View File

@ -16,17 +16,30 @@ class Cascader extends Component{
e.stopPropagation(); e.stopPropagation();
} }
optionClick(item, selected, disabled, index, e){ optionClick(item, selected, disabled, type, index, e){
if(type === 'line'){
console.log(item, index); if(disabled){
return ;
this.props.ck(item, selected, disabled); }
//加载中的不需要进行处理
let expand = this.state.expand.slice(0, index + 1); if(item.__node.loading === true){
expand[index] = item[this.props.prop.value]; return;
}
this.setState({ expand });
const { cascader, prop, sels } = this.props;
//不是父节点的不需要处理
if(!cascader.lazy && !item[prop.optgroup]){
this.props.ck(item, selected, disabled);
return
}
let expand = this.state.expand.slice(0, index + 1);
expand[index] = item[this.props.prop.value];
this.setState({ expand });
}else if(type === 'checkbox'){
this.props.ck(item, selected, disabled);
}
//阻止父组件上的事件冒泡 //阻止父组件上的事件冒泡
this.blockClick(e); this.blockClick(e);
} }
@ -47,7 +60,7 @@ class Cascader extends Component{
let { name, value, disabled, children } = prop; let { name, value, disabled, children } = prop;
const showIcon = config.model.icon != 'hidden'; const showIcon = config.model.icon != 'hidden';
const renderItem = (item, indent, index) => { const renderItem = (item, indent, index, checked) => {
//是否被选中 //是否被选中
let selected = !!sels.find(sel => sel[value] == item[value]); let selected = !!sels.find(sel => sel[value] == item[value]);
//是否禁用 //是否禁用
@ -55,8 +68,11 @@ class Cascader extends Component{
// 是否半选 // 是否半选
let half = item.__node.half === true; let half = item.__node.half === true;
selected = selected || half || item.__node.selected //是否遵义严格父子结构
dis = dis || item.__node.disabled; if(cascader.strict){
selected = selected || half || item.__node.selected
dis = dis || item.__node.disabled;
}
const iconStyle = selected ? { const iconStyle = selected ? {
color: theme.color, color: theme.color,
@ -67,8 +83,19 @@ class Cascader extends Component{
const itemStyle = { backgroundColor: 'transparent' } const itemStyle = { backgroundColor: 'transparent' }
const className = ['xm-option', (dis ? ' disabled' : ''), (selected ? ' selected' : ''), (showIcon ? 'show-icon' : 'hide-icon') ].join(' '); 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' : half ? 'xm-icon-banxuan' : 'xm-icon-duox'].join(' '); const iconClass = ['xm-option-icon xm-iconfont', radio ? 'xm-icon-danx' : cascader.strict && half ? 'xm-icon-banxuan' : 'xm-icon-duox'].join(' ');
if(item[value] === this.state.val){
itemStyle.backgroundColor = theme.hover
}
const contentStyle = {}, checkedStyle = {};
if(checked){
contentStyle.color = theme.color
contentStyle.fontWeight = 700
checkedStyle.color = theme.color
}
let checkedClass = 'xm-right-arrow';
//处理鼠标选择的背景色 //处理鼠标选择的背景色
const hoverChange = e => { const hoverChange = e => {
@ -83,31 +110,30 @@ class Cascader extends Component{
return ( return (
<div class={ className } style={ itemStyle } value={ item[value] } onClick={ <div class={ className } style={ itemStyle } value={ item[value] } onClick={
this.optionClick.bind(this, item, selected, dis, index) this.optionClick.bind(this, item, selected, dis, 'line', index)
} onMouseEnter={ hoverChange } onMouseLeave={ hoverChange }> } onMouseEnter={ hoverChange } onMouseLeave={ hoverChange }>
{ showIcon && <i class={ iconClass } style={ iconStyle } ></i> } { showIcon && <i class={ iconClass } style={ iconStyle } onClick={ this.optionClick.bind(this, item, selected, dis, 'checkbox', index) }></i> }
<div class='xm-option-content' dangerouslySetInnerHTML={{ __html: template({ data, item, arr: sels, name: item[name], value: item[value] }) }}></div> <div class='xm-option-content' style={ contentStyle } dangerouslySetInnerHTML={{ __html: template({ data, item, arr: sels, name: item[name], value: item[value] }) }}></div>
{ item[children] && <div class={ checkedClass } style={ checkedStyle }></div> }
</div> </div>
) )
} }
let boxArr = [];
const renderGroup = (item, indent, index) => { const renderGroup = (item, indent, index) => {
const child = item[children]; const child = item[children];
indent = indent + cascader.indent + 6
indent = cascader.indent + 10 const checked = child && this.state.expand[index] === item[value];
checked && boxArr.push(
return ( <div class="xm-cascader-box" index={ index % 4 } style={{ left: indent + 'px', width: cascader.indent + 'px'}}>
<div class="xm-cascader"> <div class="xm-cascader-scroll">{ child.map(c => renderGroup(c, indent, index + 1)) }</div>
{ renderItem(item, indent, index) }
{ child && this.state.expand[index] === item[value] &&
<div class="xm-cascader-box" index={ index % 4 } style={{ left: indent + 'px' }}>{ child.map(c => renderGroup(c, indent, index + 1)) }</div>
}
</div> </div>
) )
return renderItem(item, indent, index, checked)
} }
let arr = data.map(item => renderGroup(item, 0, 0)).filter(a => a); let arr = data.map(item => renderGroup(item, 2, 0)).concat(boxArr).filter(a => a);
// let safetyArr = deepMerge([], arr); // let safetyArr = deepMerge([], arr);
// let safetySels = deepMerge([], sels); // let safetySels = deepMerge([], sels);
@ -116,7 +142,7 @@ class Cascader extends Component{
} }
return ( return (
<div onClick={ this.blockClick } class="xm-body-cascader"> <div onClick={ this.blockClick } class="xm-body-cascader" style={{ width: cascader.indent + 'px', height: config.height }}>
{ arr } { arr }
</div> </div>
) )

View File

@ -194,7 +194,7 @@ class General extends Component{
} }
//组件将要接收新属性 //组件将要接收新属性
componentWillReceiveProps(props){ componentWillReceiveProps(props){
if(this.props.show != props.show){ if(this.props.show != props.show){
if(!props.show){ if(!props.show){
//清空输入框的值 //清空输入框的值

View File

@ -237,7 +237,7 @@ class Tree extends Component{
} }
const className = ['xm-option', (dis ? ' disabled' : ''), (selected ? ' selected' : ''), (showIcon ? 'show-icon' : 'hide-icon') ].join(' '); 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 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 || (tree.lazy && item.__node.loading !== false)) ? 'visible':'hidden'].join(' '); const treeIconClass = ['xm-tree-icon', expand ? 'expand':'', item[children] && (item[children].length > 0 || (tree.lazy && item.__node.loading !== false)) ? 'xm-visible':'xm-hidden'].join(' ');
const iconArray = []; const iconArray = [];
if(tree.showFolderIcon){ if(tree.showFolderIcon){
@ -284,7 +284,7 @@ class Tree extends Component{
child.length === 0 && (expand = false) child.length === 0 && (expand = false)
return ( return (
<div class="xm-tree"> <div class="xm-tree">
{ tree.showFolderIcon && tree.showLine && expand && child.length > 1 && <i class='left-line left-line-group' style={ {left: indent + 3 + 'px'} }></i> } { tree.showFolderIcon && tree.showLine && expand && child.length > 0 && <i class='left-line left-line-group' style={ {left: indent + 3 + 'px'} }></i> }
{ renderItem(item, indent, child.length === 0 && (!tree.lazy || tree.lazy && item.__node.loading === false) ? 0 : expand) } { renderItem(item, indent, child.length === 0 && (!tree.lazy || tree.lazy && item.__node.loading === false) ? 0 : expand) }
{ expand && <div class="xm-tree-box">{ child.map(c => renderGroup(c, indent)) }</div> } { expand && <div class="xm-tree-box">{ child.map(c => renderGroup(c, indent)) }</div> }
</div> </div>
@ -311,7 +311,7 @@ class Tree extends Component{
//工具条操作 //工具条操作
function flat(list, array){ function flat(list, array){
array.forEach(item => item[optgroup] ? flat(list, item[children]) : list.push(item)) array.forEach(item => item[optgroup] ? (!tree.strict && list.push(item), flat(list, item[children])) : list.push(item))
} }
const toolbar = ( const toolbar = (
<div class='xm-toolbar'> <div class='xm-toolbar'>

View File

@ -107,7 +107,9 @@ export default function (lan = 'zn') {
//是否展示级联 //是否展示级联
show: false, show: false,
//间距 //间距
indent: 200, indent: 100,
//是否严格遵守父子模式
strict: true,
}, },
//自定义属性名称 //自定义属性名称
prop: { prop: {

View File

@ -401,7 +401,7 @@ xm-select{
margin-left: -2px; margin-left: -2px;
transform: rotate(90deg); transform: rotate(90deg);
} }
&.visible{ &.xm-visible{
visibility: visible; visibility: visible;
} }
} }
@ -429,36 +429,57 @@ xm-select{
.xm-cascader{ .xm-cascader{
// position: relative; // position: relative;
&-box{ &-box{
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
padding: 5px 0;
border: @border;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12);
margin: -1px;
&::before{
content: ' ';
position: absolute;
width: 0;
height: 0;
border: 6px solid transparent;
border-right-color: @defaultBorderColor;
top: 10px;
left: -12px;
}
&::after{
content: ' ';
position: absolute;
width: 0;
height: 0;
border: 6px solid transparent;
border-right-color: #fff;
top: 10px;
left: -11px;
}
}
&-scroll{
height: 100%;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
padding: 5px 0;
}
&-box[index="0"]{
background-color: #F5F5F5;
}
&-box[index="1"]{
background-color: #FAFAFA;
}
&-box[index="2"]{
background-color: #FFFFFF;
}
&-box[index="3"]{
background-color: #EFEFEF;
} }
} }
&.cascader{ &.cascader{
background-color: #EFEFEF; width: unset;
min-width: unset;
.xm-option-content{
padding-left: 8px;
}
.disabled .xm-right-arrow{
color: #C2C2C2 !important;
}
} }
} }
.xm-input{ .xm-input{
@ -533,6 +554,19 @@ xm-select{
cursor: initial; cursor: initial;
} }
.xm-right-arrow{
position: absolute;
color: #666;
right: 5px;
top: -1px;
font-weight: 700;
transform: scale(0.6, 1);
&::after{
content: '>';
}
}
} }
//不同尺寸下的数据调整 //不同尺寸下的数据调整
@ -573,7 +607,7 @@ xm-select{
.left-line-group{ .left-line-group{
height: calc(100% - @size); height: calc(100% - @size);
} }
.xm-tree-icon.hidden+.top-line{ .xm-tree-icon.xm-hidden+.top-line{
top: @size / 2 - 1px; top: @size / 2 - 1px;
} }
} }