Compare commits
10 Commits
16de57e340
...
e7e9ee2a65
Author | SHA1 | Date | |
---|---|---|---|
e7e9ee2a65 | |||
|
05dc278d28 | ||
|
170d8ab0d1 | ||
|
8774b1f79c | ||
|
479b26fa7f | ||
|
eb4e6fa66e | ||
|
12b7f0ce0e | ||
|
f8fa44c94c | ||
|
e928c6a038 | ||
|
b47c8cfcb9 |
54
CHANGELOG.md
54
CHANGELOG.md
@ -1,5 +1,59 @@
|
||||
## 更新日志
|
||||
|
||||
### 1.2.4
|
||||
|
||||
*2021-07-22*
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增渲染完成的回调`done`
|
||||
- `create`支持一次性创建多个, 返回一个数组即可 [#I40MU0](https://gitee.com/maplemei/xm-select/issues/I40MU0)
|
||||
- 增加预选配置`enableHoverFirst`, 默认选中第一项可选中数据 [#I3ZCRX](https://gitee.com/maplemei/xm-select/issues/I3ZCRX)
|
||||
- 新增focus后的样式提醒
|
||||
- 新增自定义设置选中键盘KeyCode配置`selectedKeyCode`, 可选 `xmSelect.KeyCode.Enter`, `xmSelect.KeyCode.Space`, 也可自行定义数值 [#I41NNI](https://gitee.com/maplemei/xm-select/issues/I41NNI)
|
||||
- 表单验证失败后 滚动到可视范围内 ##灰度##
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- 修复`tree`非严格模式下, 无法选中父节点
|
||||
- 修复`tree`模式下搜索后无法折叠的问题 [#I3XBF1](https://gitee.com/maplemei/xm-select/issues/I3XBF1)
|
||||
|
||||
|
||||
### 1.2.3
|
||||
|
||||
*2021-06-30*
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增`submitConversion`配置方法, 用于拓展表单提交数据, 默认是value数组
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- 修复级联模式下第一组数据过多时不显示滚动条
|
||||
- 修复级联模式下隐藏图标背景色透明的bug
|
||||
- 修复级联模式下如果子节点是空数组也显示右箭头的bug
|
||||
- 修复级联/树模式下,如果子节点是空数组,然后操作选中状态异常
|
||||
- 修复工具条点击清空, `on`监听到的`isAdd`为`true`的bug [#I3T2KE](https://gitee.com/maplemei/xm-select/issues/I3T2KE)
|
||||
- 修复setValue时对多选上限的判断异常 [#I3SABO](https://gitee.com/maplemei/xm-select/issues/I3SABO)
|
||||
|
||||
|
||||
### 1.2.2
|
||||
|
||||
*2021-01-19*
|
||||
|
||||
#### 新增
|
||||
|
||||
- 新增配置 `model.type: fixed`, 切换为`fixed`布局模式 [体验传送门](https://maplemei.gitee.io/xm-select/#/senior/table)
|
||||
- 新增实例方法`calcPosition`, fixed布局模式下重新计算位置
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- 修改直接设置父节点无法选中的问题
|
||||
- 修改非严格模式下设置父节点, 子节点受影响
|
||||
- 修复渲染失败页面监听错误的问题
|
||||
- 修改数据重复时分组错乱的问题
|
||||
|
||||
|
||||
### 1.2.1
|
||||
|
||||
*2020-11-27*
|
||||
|
279
README.md
279
README.md
@ -1,76 +1,79 @@
|
||||
# xm-select
|
||||
|
||||
#### 介绍
|
||||
始于Layui, 下拉选择框的多选解决方案
|
||||
|
||||
前身[前往formSelectes](https://github.com/hnzzmsf/layui-formSelects), 由于渲染速度慢, 代码冗余, 被放弃了
|
||||
|
||||
`xm-select`使用了新的开发方式, 利用preact进行渲染, 大幅度提高渲染速度, 并且可以灵活拓展
|
||||
|
||||
[xm-select演示站点](https://maplemei.gitee.io/xm-select/)
|
||||
|
||||
> 支持功能
|
||||
|
||||
- [x] 国际化 - 中文/英文
|
||||
- [x] 多选
|
||||
- [x] 单选
|
||||
- [x] 重复选
|
||||
- [x] 分组
|
||||
- [x] 工具条
|
||||
- [x] 创建条目
|
||||
- [x] 显示模式
|
||||
- [x] 搜索模式 (本地数据过滤, 远程搜索)
|
||||
- [x] 分页模式
|
||||
- [x] 下拉树
|
||||
- [x] 下拉任意 - 可以自己写html
|
||||
|
||||
> 联系方式
|
||||
|
||||
- xm-select技术群①: `660408068` (500人)
|
||||
- xm-select技术群②: `938624691` (500人)
|
||||
- xm-select技术群③: `1145047250` (500人)
|
||||
|
||||
[issues 需求记录](https://gitee.com/maplemei/xm-select/issues/I1NSO7)
|
||||
|
||||
|
||||
[更新日志](CHANGELOG.md)
|
||||
|
||||
|
||||
#### 软件架构
|
||||
1. 引入第三方[preact](https://preactjs.com/)库, 利用jsx渲染页面结构
|
||||
2. 使用[webpack](https://www.webpackjs.com/)进行打包
|
||||
|
||||
|
||||
#### 快读上手
|
||||
|
||||
> 直接使用
|
||||
|
||||
```
|
||||
1. 引入 `dist/xm-select.js`
|
||||
2. 写一个`<div id="demo1"></div>`
|
||||
3. 渲染
|
||||
var demo1 = xmSelect.render({
|
||||
el: '#demo1',
|
||||
data: [
|
||||
{name: '水果', value: 1, selected: true, disabled: true},
|
||||
{name: '蔬菜', value: 2, selected: true},
|
||||
{name: '桌子', value: 3, disabled: true},
|
||||
{name: '北京', value: 4},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
> 二次开发
|
||||
|
||||
```
|
||||
1. git clone https://gitee.com/maplemei/xm-select.git
|
||||
2. cd xm-select
|
||||
3. yarn 或者 npm install
|
||||
```
|
||||
|
||||
[目录结构说明](https://gitee.com/maplemei/xm-select/wikis/pages?sort_id=2465940&doc_id=820743)
|
||||
|
||||
|
||||
# xm-select
|
||||
|
||||
#### 介绍
|
||||
始于Layui, 下拉选择框的多选解决方案
|
||||
|
||||
前身[前往formSelectes](https://github.com/hnzzmsf/layui-formSelects), 由于渲染速度慢, 代码冗余, 被放弃了
|
||||
|
||||
`xm-select`使用了新的开发方式, 利用preact进行渲染, 大幅度提高渲染速度, 并且可以灵活拓展
|
||||
|
||||
[xm-select演示站点](https://maplemei.gitee.io/xm-select/)
|
||||
|
||||
> 支持功能
|
||||
|
||||
- [x] 国际化 - 中文/英文
|
||||
- [x] 多选
|
||||
- [x] 单选
|
||||
- [x] 重复选
|
||||
- [x] 分组
|
||||
- [x] 工具条
|
||||
- [x] 创建条目
|
||||
- [x] 显示模式
|
||||
- [x] 搜索模式 (本地数据过滤, 远程搜索)
|
||||
- [x] 分页模式
|
||||
- [x] 下拉树
|
||||
- [x] 下拉任意 - 可以自己写html
|
||||
|
||||
> 联系方式
|
||||
|
||||

|
||||
|
||||
|
||||
- xm-select技术群①: `660408068` (500人)
|
||||
- xm-select技术群②: `938624691` (500人)
|
||||
- xm-select技术群③: `1145047250` (500人)
|
||||
|
||||
[issues 需求记录](https://gitee.com/maplemei/xm-select/issues)
|
||||
|
||||
|
||||
[更新日志](CHANGELOG.md)
|
||||
|
||||
|
||||
#### 软件架构
|
||||
1. 引入第三方[preact](https://preactjs.com/)库, 利用jsx渲染页面结构
|
||||
2. 使用[webpack](https://www.webpackjs.com/)进行打包
|
||||
|
||||
|
||||
#### 快读上手
|
||||
|
||||
> 直接使用
|
||||
|
||||
```
|
||||
1. 引入 `dist/xm-select.js`
|
||||
2. 写一个`<div id="demo1"></div>`
|
||||
3. 渲染
|
||||
var demo1 = xmSelect.render({
|
||||
el: '#demo1',
|
||||
data: [
|
||||
{name: '水果', value: 1, selected: true, disabled: true},
|
||||
{name: '蔬菜', value: 2, selected: true},
|
||||
{name: '桌子', value: 3, disabled: true},
|
||||
{name: '北京', value: 4},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
> 二次开发
|
||||
|
||||
```
|
||||
1. git clone https://gitee.com/maplemei/xm-select.git
|
||||
2. cd xm-select
|
||||
3. yarn 或者 npm install
|
||||
```
|
||||
|
||||
[目录结构说明](https://gitee.com/maplemei/xm-select/wikis/pages?sort_id=2465940&doc_id=820743)
|
||||
|
||||
|
||||
> 打赏
|
||||
|
||||
如果喜欢作者的插件, 可以请作者吃雪糕 ^_^
|
||||
@ -80,68 +83,68 @@
|
||||
<img src="docs/assets/wx.jpg" alt="打赏" width="300" style="border: 1px solid #EFEFEF">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
#### 示例
|
||||
|
||||
[示例页面](https://maplemei.gitee.io/xm-select/)
|
||||
|
||||
> 一个小栗子
|
||||
|
||||
```
|
||||
<!-- 占位 -->
|
||||
<div id="demo1"></div>
|
||||
|
||||
|
||||
<!-- 引入插件 -->
|
||||
<script src="../dist/xm-select.js" type="text/javascript" charset="utf-8"></script>
|
||||
<!-- 渲染页面 -->
|
||||
<script type="text/javascript">
|
||||
var demo1 = xmSelect.render({
|
||||
// 这里绑定css选择器
|
||||
el: '#demo1',
|
||||
// 渲染的数据
|
||||
data: [
|
||||
{name: '水果', value: 1, selected: true, disabled: true},
|
||||
{name: '蔬菜', value: 2, selected: true},
|
||||
{name: '桌子', value: 3, disabled: true},
|
||||
{name: '北京', value: 4},
|
||||
],
|
||||
})
|
||||
|
||||
// 变量, demo1 可以通过API操作
|
||||
// 获取选中值, demo1.getValue();
|
||||
// 设置选中值, demo1.setValue([{ name: '动态值', value: 999 }])
|
||||
// ...
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 相关
|
||||
|
||||
> 支持IE吗
|
||||
|
||||
简单适配IE10以上的版本, 如有其它兼容性问题, 请加群反馈
|
||||
|
||||
> 为什么没有css文件
|
||||
|
||||
已经内置到js代码中了, 直接引入`xm-select.js`即可使用
|
||||
|
||||
> 开源 != 无私
|
||||
|
||||
有问题请自己多动手尝试^_^
|
||||
|
||||
> 成长之路
|
||||
|
||||
```
|
||||
maplemei, 一个90后, 热爱前端的程序猿
|
||||
|
||||
16年接触了 贤心大大 的 layui, 开始走向了前端的不归之路
|
||||
17年尝试自己写了一个基于layui的省市区联动插件, 同年底开发了layui select多选第一版
|
||||
18年6月发布了formSelects
|
||||
19年6月发布了xm-select
|
||||
|
||||
其实每个版本的更新都是自己对前端的一个新的认知, 也是一个新的学习之路
|
||||
|
||||
目前作者几乎不怎么使用layui, 已经走向了vue, react的新途 , xm-select的维护是对layui的一种情怀 ^_^
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 示例
|
||||
|
||||
[示例页面](https://maplemei.gitee.io/xm-select/)
|
||||
|
||||
> 一个小栗子
|
||||
|
||||
```
|
||||
<!-- 占位 -->
|
||||
<div id="demo1"></div>
|
||||
|
||||
|
||||
<!-- 引入插件 -->
|
||||
<script src="../dist/xm-select.js" type="text/javascript" charset="utf-8"></script>
|
||||
<!-- 渲染页面 -->
|
||||
<script type="text/javascript">
|
||||
var demo1 = xmSelect.render({
|
||||
// 这里绑定css选择器
|
||||
el: '#demo1',
|
||||
// 渲染的数据
|
||||
data: [
|
||||
{name: '水果', value: 1, selected: true, disabled: true},
|
||||
{name: '蔬菜', value: 2, selected: true},
|
||||
{name: '桌子', value: 3, disabled: true},
|
||||
{name: '北京', value: 4},
|
||||
],
|
||||
})
|
||||
|
||||
// 变量, demo1 可以通过API操作
|
||||
// 获取选中值, demo1.getValue();
|
||||
// 设置选中值, demo1.setValue([{ name: '动态值', value: 999 }])
|
||||
// ...
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 相关
|
||||
|
||||
> 支持IE吗
|
||||
|
||||
简单适配IE10以上的版本, 如有其它兼容性问题, 请加群反馈
|
||||
|
||||
> 为什么没有css文件
|
||||
|
||||
已经内置到js代码中了, 直接引入`xm-select.js`即可使用
|
||||
|
||||
> 开源 != 无私
|
||||
|
||||
有问题请自己多动手尝试^_^
|
||||
|
||||
> 成长之路
|
||||
|
||||
```
|
||||
maplemei, 一个90后, 热爱前端的程序猿
|
||||
|
||||
16年接触了 贤心大大 的 layui, 开始走向了前端的不归之路
|
||||
17年尝试自己写了一个基于layui的省市区联动插件, 同年底开发了layui select多选第一版
|
||||
18年6月发布了formSelects
|
||||
19年6月发布了xm-select
|
||||
|
||||
其实每个版本的更新都是自己对前端的一个新的认知, 也是一个新的学习之路
|
||||
|
||||
目前作者几乎不怎么使用layui, 已经走向了vue, react的新途 , xm-select的维护是对layui的一种情怀 ^_^
|
||||
```
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const pkg = require('../package.json');
|
||||
const pkg = require('../package.json');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
@ -7,14 +7,14 @@ const {
|
||||
CleanWebpackPlugin
|
||||
} = require('clean-webpack-plugin');
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'prod';
|
||||
|
||||
const banner =
|
||||
`@Title: ${pkg.name}
|
||||
@Version: ${pkg.version}
|
||||
@Description:基于layui的多选解决方案
|
||||
@Site: https://gitee.com/maplemei/xm-select
|
||||
@Author: maplemei
|
||||
const isProd = process.env.NODE_ENV === 'prod';
|
||||
|
||||
const banner =
|
||||
`@Title: ${pkg.name}
|
||||
@Version: ${pkg.version}
|
||||
@Description:基于layui的多选解决方案
|
||||
@Site: https://gitee.com/maplemei/xm-select
|
||||
@Author: maplemei
|
||||
@License:Apache License 2.0`;
|
||||
|
||||
const webpackConfig = {
|
||||
@ -90,13 +90,16 @@ const webpackConfig = {
|
||||
minify: {
|
||||
collapseWhitespace: true
|
||||
}
|
||||
}),
|
||||
new webpack.BannerPlugin(banner),
|
||||
}),
|
||||
new webpack.BannerPlugin(banner),
|
||||
new VueLoaderPlugin(),
|
||||
],
|
||||
optimization: {
|
||||
minimize: false,//可以自行配置是否压缩
|
||||
},
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
port: 9000,
|
||||
port: 9001,
|
||||
publicPath: '/',
|
||||
hot: true
|
||||
},
|
||||
@ -109,4 +112,4 @@ if (isProd) {
|
||||
}
|
||||
|
||||
|
||||
module.exports = webpackConfig;
|
||||
module.exports = webpackConfig;
|
||||
|
13471
dist/static/2.js
vendored
13471
dist/static/2.js
vendored
File diff suppressed because one or more lines are too long
1128
dist/static/3.js
vendored
1128
dist/static/3.js
vendored
File diff suppressed because one or more lines are too long
120377
dist/static/docs.js
vendored
120377
dist/static/docs.js
vendored
File diff suppressed because one or more lines are too long
8
dist/xm-select.js
vendored
8
dist/xm-select.js
vendored
File diff suppressed because one or more lines are too long
@ -38,6 +38,8 @@
|
||||
this.fixedControl = bottom > document.documentElement.clientHeight &&
|
||||
top + 44 <= document.documentElement.clientHeight;
|
||||
this.$refs.control.style.left = this.fixedControl ? `${ left }px` : '0';
|
||||
|
||||
xmSelect.get().forEach(xs => xs.calcPosition());
|
||||
},
|
||||
removeScrollHandler() {
|
||||
this.scrollParent && this.scrollParent.removeEventListener('scroll', this.scrollHandler);
|
||||
|
@ -4,10 +4,7 @@
|
||||
<div class="container">
|
||||
<h1>
|
||||
<router-link :to="`/`">
|
||||
<!-- logo -->
|
||||
<slot>
|
||||
<!-- <img src="../assets/images/element-logo.svg" alt="element-logo" class="nav-logo"> -->
|
||||
<!-- <img src="../assets/images/element-logo-small.svg" alt="element-logo" class="nav-logo-small"> -->
|
||||
xm-select
|
||||
</slot>
|
||||
</router-link>
|
||||
@ -19,7 +16,7 @@
|
||||
<router-link active-class="active" :to="`/`">使用手册</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href='https://gitee.com/maplemei/xm-select/issues/I1NSO7' target="_blank" style="opacity: 1;">提新需求↗</a>
|
||||
<a href='https://gitee.com/maplemei/xm-select/issues' target="_blank" style="opacity: 1;">提新需求↗</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
|
@ -48,7 +48,7 @@ layui.use('table', function() {
|
||||
//修改一些css样式, 这里虽然能够使用, 但是还是不太友好, 努力中...
|
||||
var cells = document.querySelectorAll('div[lay-id="demo"] .layui-table-cell');
|
||||
for(var i = 0 ; i < cells.length ; i++ ){
|
||||
cells[i].style.overflow = 'unset';
|
||||
//cells[i].style.overflow = 'unset';
|
||||
cells[i].style.height = 'auto';
|
||||
}
|
||||
//渲染多选
|
||||
@ -56,6 +56,7 @@ layui.use('table', function() {
|
||||
var xm = xmSelect.render({
|
||||
el: '#XM-' + item.id,
|
||||
autoRow: true,
|
||||
model: { type: 'fixed' },
|
||||
data: [
|
||||
{name: '张三', value: 1},
|
||||
{name: '李四', value: 2},
|
||||
@ -70,6 +71,13 @@ layui.use('table', function() {
|
||||
|
||||
});
|
||||
|
||||
//表格滚动时 重新计算位置
|
||||
document.querySelector('.layui-table-body').addEventListener('scroll', () => {
|
||||
xmSelect.get().forEach(function(item){
|
||||
item.calcPosition();
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
```
|
||||
|
@ -5,46 +5,55 @@
|
||||
<div id="demo1" class="xm-select-demo"></div>
|
||||
|
||||
<script>
|
||||
var demo1 = xmSelect.render({
|
||||
xmSelect.render({
|
||||
el: '#demo1',
|
||||
autoRow: true,
|
||||
filterable: true,
|
||||
tree: {
|
||||
show: true,
|
||||
showFolderIcon: true,
|
||||
showLine: true,
|
||||
indent: 20,
|
||||
expandedKeys: [ -3 ],
|
||||
simple: true,
|
||||
clickExpand: false,
|
||||
clickCheck: false,
|
||||
strict: false
|
||||
},
|
||||
toolbar: {
|
||||
show: true,
|
||||
list: ['ALL', 'REVERSE', 'CLEAR']
|
||||
height: '200px',
|
||||
maxMethod(a, item){
|
||||
console.log(item)
|
||||
},
|
||||
submitConversion(sels, prop){
|
||||
return sels.map(item => item[prop.name]).join(',')
|
||||
},
|
||||
filterable: true,
|
||||
height: 'auto',
|
||||
data(){
|
||||
return [
|
||||
{name: '销售员', value: -1, disabled: true, children: [
|
||||
{name: '销售员', value: -1, disabled: false, children: [
|
||||
{name: '张三1', value: 1, selected: true, children: []},
|
||||
{name: '李四1', value: 2, selected: true},
|
||||
{name: '王五1', value: 3, disabled: true},
|
||||
{name: '王五1', value: 13, disabled: true},
|
||||
{name: '王五1', value: 131, disabled: true},
|
||||
{name: '王五1', value: 132, disabled: true},
|
||||
{name: '王五1', value: 133, disabled: true},
|
||||
{name: '王五1', value: 134, disabled: true},
|
||||
{name: '王五1', value: 135, disabled: true},
|
||||
{name: '王五1', value: 136, disabled: true},
|
||||
{name: '王五1', value: 137, disabled: true},
|
||||
{name: '王五1', value: 138, disabled: true},
|
||||
]},
|
||||
{name: '奖品', value: -2, disabled: true, children: [
|
||||
{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: 4, disabled: true},
|
||||
{name: '香蕉2', value: 5},
|
||||
{name: '葡萄2', value: 6},
|
||||
]},
|
||||
{name: '李四1', value: 2},
|
||||
{name: '王五1', value: 3, disabled: true},
|
||||
{name: '王五1', value: 31, disabled: true},
|
||||
{name: '王五1', value: 32, disabled: true},
|
||||
{name: '王五1', value: 33, disabled: true},
|
||||
{name: '王五1', value: 34, disabled: true},
|
||||
{name: '王五1', value: 35, disabled: true},
|
||||
{name: '王五1', value: 36, disabled: true},
|
||||
{name: '王五1', value: 37, disabled: true},
|
||||
{name: '王五1', value: 38, disabled: true},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
@ -46,12 +46,16 @@
|
||||
| toolbar | 工具条, 具体看下表 | object | - | - |
|
||||
| showCount | 展示在下拉框中的最多选项数量 | int | - | 0 |
|
||||
| enableKeyboard | 是否启用键盘操作选项 | boolean | true / false | true |
|
||||
| enableHoverFirst | 是否默认选中第一项 | boolean | true / false | true |
|
||||
| selectedKeyCode | 选中的键盘KeyCode | int | 全部KeyCode, 也可xmSelect.KeyCode.Enter,xmSelect.KeyCode.Space | 13 |
|
||||
| autoRow | 是否开启自动换行(选项过多时) | boolean | true / false | false |
|
||||
| size | 尺寸 | string | large / medium / small / mini | medium |
|
||||
| disabled | 是否禁用多选 | boolean | true / false | false |
|
||||
| create | 创建条目 | function(val, data), val: 搜索的数据, data: 当前下拉数据 | - | null |
|
||||
| tree | 树形结构, 具体看下表 | object | - | - |
|
||||
| cascader | 级联结构, 具体看下表 | object | - | - |
|
||||
| submitConversion | 配置表单提交数据 | function(sels, prop), sels: 已选中数据, prop: 自定义的prop | - | - |
|
||||
| done | 渲染完成回调 | function | - | - |
|
||||
|
||||
|
||||
### prop
|
||||
@ -134,7 +138,7 @@ model: {
|
||||
}
|
||||
},
|
||||
},
|
||||
//展示类型, 下拉框形式: absolute, 直接显示模式: relative
|
||||
//展示类型, 下拉框形式: absolute, 直接显示模式: relative, 浮动布局: fixed
|
||||
type: 'absolute',
|
||||
},
|
||||
```
|
||||
@ -211,7 +215,7 @@ list: [ "ALL", "CLEAR",
|
||||
| render | 渲染多选 | (options: 配置项) | 实例对象 |
|
||||
| get | 获取页面中已经渲染的多选 | (filter: 过滤`el`, single: 是否返回单实例) | 符合条件的实例数组 |
|
||||
| batch | 批量操作已渲染的多选 | (filter: 过滤`el`, method: 方法, ...方法参数) | 符合条件的实例数组 |
|
||||
| arrr2tree | 把列表数据转化为树状结构 | (arr: 数据, pid: 父节点ID的key, id: 对应key, children: 对应key, topParentId: 顶级节点的ID) | 符合条件的数组 |
|
||||
| arr2tree | 把列表数据转化为树状结构 | (arr: 数据, pid: 父节点ID的key, id: 对应key, children: 对应key, topParentId: 顶级节点的ID) | 符合条件的数组 |
|
||||
|
||||
```
|
||||
//render 使用方式
|
||||
@ -258,3 +262,4 @@ xmSelect.render()后会返回一个xmSelect对象, 可以进行方法调用
|
||||
| changeExpandedKeys | 树模式下更新节点展开状态, v1.2.0 新增 | (keys: true-全部展开, false-全部关闭, 数组-展开的节点值) |
|
||||
| enable | 启用选项, disabled=false, v1.2.0 新增 | (array: 想要启用的选项数组) |
|
||||
| disable | 禁用用选项, disabled=true, v1.2.0 新增 | (array: 想要禁用的选项数组) |
|
||||
| calcPosition | fixed布局模式下重新计算位置, v1.2.2 新增 | - |
|
||||
|
9070
package-lock.json
generated
Normal file
9070
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,45 +1,46 @@
|
||||
{
|
||||
"name": "xm-select",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.4",
|
||||
"description": "始于Layui的select多选解决方案",
|
||||
"website": "https://maplemei.gitee.io/xm-select",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=dev node_modules/.bin/webpack-dev-server --config build/webpack.config.js",
|
||||
"build": "cross-env NODE_ENV=prod webpack --config build/webpack.config.js"
|
||||
},
|
||||
"author": "",
|
||||
"author": "maplemei",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"element-ui": "^2.12.0",
|
||||
"highlight.js": "^9.15.10",
|
||||
"preact": "^10.4.6",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.0",
|
||||
"@babel/preset-env": "^7.4.2",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"cross-env": "^6.0.0",
|
||||
"css-loader": "^3.0.0",
|
||||
"element-ui": "^2.12.0",
|
||||
"file-loader": "^4.2.0",
|
||||
"highlight.js": "^9.15.10",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.1.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"markdown-it-anchor": "^5.2.4",
|
||||
"markdown-it-chain": "^1.3.0",
|
||||
"markdown-it-container": "^2.0.0",
|
||||
"preact": "^10.4.6",
|
||||
"style-loader": "^0.23.1",
|
||||
"transliteration": "^2.1.7",
|
||||
"url-loader": "^2.1.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-loader": "^15.7.1",
|
||||
"vue-router": "^3.1.3",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.29.6",
|
||||
"webpack-cli": "^3.3.0",
|
||||
"webpack-dev-server": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"html-webpack-plugin": "^3.2.0"
|
||||
}
|
||||
}
|
||||
|
6638
pnpm-lock.yaml
generated
Normal file
6638
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -163,31 +163,49 @@ export function exchangeOptionsData(arr, { prop }){
|
||||
}
|
||||
}
|
||||
return newArr;
|
||||
}
|
||||
|
||||
export function toSimple(data, sels, list, prop){
|
||||
if(!data || !isArray(data)){
|
||||
return;
|
||||
}
|
||||
|
||||
let { children, selected, value } = prop;
|
||||
data.forEach(item => {
|
||||
if(item.__node[selected] || sels.find(i => i[value] === item[value])){
|
||||
list.push(item);
|
||||
}else{
|
||||
toSimple(item[children], sels, list, prop);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function delProp(data, children, props){
|
||||
if(!data || !isArray(data)){
|
||||
return;
|
||||
}
|
||||
return data.map(item => {
|
||||
item = { ...item };
|
||||
props.forEach(prop => delete item[prop]);
|
||||
item[children] = delProp(item[children], children, props);
|
||||
return item;
|
||||
})
|
||||
}
|
||||
|
||||
export function toSimple(data, sels, list, prop){
|
||||
if(!data || !isArray(data)){
|
||||
return;
|
||||
}
|
||||
|
||||
let { children, selected, value } = prop;
|
||||
data.forEach(item => {
|
||||
if(item.__node[selected] || sels.find(i => i[value] === item[value])){
|
||||
list.push(item);
|
||||
}else{
|
||||
toSimple(item[children], sels, list, prop);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function delProp(data, children, props){
|
||||
if(!data || !isArray(data)){
|
||||
return;
|
||||
}
|
||||
return data.map(item => {
|
||||
item = { ...item };
|
||||
props.forEach(prop => delete item[prop]);
|
||||
item[children] = delProp(item[children], children, props);
|
||||
return item;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function throttle(cb, time = 100 ,wait = true) {
|
||||
// cb => 回调函数
|
||||
// time => 定时
|
||||
// wait => 是否等待 false 立即生效 true 时间到了再执行
|
||||
const self = this // 保存this指向
|
||||
let disable = false // 节流标志
|
||||
return function (...data) {
|
||||
if(disable)return // 如果是禁止状态那么直接跳出
|
||||
disable = true // 如果不是禁止状态那么立即设置为禁止
|
||||
!wait && cb.call(self,...data) // 如果不等待那么立即执行
|
||||
setTimeout(()=>{
|
||||
wait && cb.call(self,...data) // 如果要等待那么到时间后再执行
|
||||
disable = false // 关闭禁止状态
|
||||
},time)
|
||||
}
|
||||
}
|
@ -31,11 +31,12 @@ class Framework extends Component{
|
||||
show: false,
|
||||
tmpColor: '',
|
||||
bodyClass: '',
|
||||
time: 0,
|
||||
}
|
||||
}
|
||||
|
||||
init(props, refresh){
|
||||
let { data, prop, initValue, radio } = props, sels;
|
||||
let { data, prop, initValue, radio, tree, cascader } = props, sels;
|
||||
//如果新数据和旧数据不同 或者 强制刷新 才进行数据处理
|
||||
if(refresh){
|
||||
let dataObj = {};
|
||||
@ -47,6 +48,9 @@ class Framework extends Component{
|
||||
}), dataObj)
|
||||
if(radio && sels.length > 1){
|
||||
sels = sels.slice(0, 1)
|
||||
if(tree.show && tree.strict || cascader.show && cascader.strict){
|
||||
this.clearAndReset(data, sels, false);
|
||||
}
|
||||
}
|
||||
this.setState({ sels, dataObj, flatData });
|
||||
}
|
||||
@ -116,7 +120,7 @@ class Framework extends Component{
|
||||
return cgList;
|
||||
}
|
||||
|
||||
value(sels, show, listenOn, jsChangeData){
|
||||
value(sels, show, listenOn, jsChangeData, isAdd = true){
|
||||
if(show !== false && show !== true){
|
||||
show = this.state.show;
|
||||
}
|
||||
@ -125,7 +129,7 @@ class Framework extends Component{
|
||||
let changeData = this.exchangeValue(sels);
|
||||
|
||||
//检测是否超选了
|
||||
if(this.checkMax(changeData, changeData)){
|
||||
if(this.checkMax(changeData, changeData, true)){
|
||||
return ;
|
||||
}
|
||||
|
||||
@ -134,19 +138,28 @@ class Framework extends Component{
|
||||
this.clearAndReset(data, changeData, false);
|
||||
changeData = this.init({ data, prop }, true);
|
||||
}
|
||||
this.resetSelectValue(changeData, jsChangeData ? jsChangeData : changeData, true, listenOn);
|
||||
this.resetSelectValue(changeData, jsChangeData ? jsChangeData : changeData, isAdd, listenOn);
|
||||
this.setState({ show })
|
||||
}
|
||||
|
||||
clearAndReset(data, changeData, parentCK){
|
||||
const { selected, children, value } = this.props.prop;
|
||||
const { selected, disabled, children, value } = this.props.prop;
|
||||
data.forEach(item => {
|
||||
item[selected] = changeData.findIndex(c => c[value] === item[value]) != -1 || parentCK;
|
||||
let child = item[children];
|
||||
child && isArray(child) && this.clearAndReset(child, changeData, item[selected])
|
||||
if(child && isArray(child) && child.length > 0){
|
||||
this.clearAndReset(child, changeData, item[selected])
|
||||
let len = child.length;
|
||||
let slen = child.filter(i => i[selected] === true || i.__node.selected === true).length;
|
||||
item.__node.selected = slen === len;
|
||||
item.__node.half = slen > 0 && slen < len || child.filter(i => i.__node.half === true).length > 0;
|
||||
item.__node.disabled = child.filter(i => i[disabled] === true || i.__node.disabled === true).length === len;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
load(data, dataObj, flatData, parent, level = 0, initValue){
|
||||
const { prop, tree, cascader } = this.props;
|
||||
const { children, optgroup, value, selected, disabled } = prop;
|
||||
@ -237,11 +250,12 @@ class Framework extends Component{
|
||||
}
|
||||
}
|
||||
|
||||
checkMax(item, sels){
|
||||
checkMax(item, sels, contains){
|
||||
const { max, maxMethod, theme } = this.props
|
||||
//查看是否设置了多选上限
|
||||
let maxCount = toNum(max);
|
||||
if(maxCount > 0 && sels.length >= maxCount){
|
||||
let flag = (contains ? sels.length : (isArray(item) ? item.length : 1) + sels.length) > maxCount;
|
||||
if(maxCount > 0 && flag){
|
||||
this.updateBorderColor(theme.maxColor);
|
||||
//查看是否需要回调
|
||||
maxMethod && isFunction(maxMethod) && maxMethod(sels, item);
|
||||
@ -252,14 +266,14 @@ class Framework extends Component{
|
||||
//选项, 选中状态, 禁用状态, 是否强制删除:在label上点击删除
|
||||
itemClick(item, itemSelected, itemDisabled, mandatoryDelete){
|
||||
|
||||
const { theme, prop, radio, repeat, clickClose, max, maxMethod, tree } = this.props
|
||||
const { theme, prop, radio, repeat, clickClose, max, maxMethod, tree, cascader, data } = this.props
|
||||
let sels = [ ...this.state.sels ]
|
||||
const { value, selected, disabled, children, optgroup } = prop
|
||||
|
||||
//如果是禁用状态, 不能进行操作
|
||||
if(itemDisabled) return;
|
||||
|
||||
if(item[optgroup] && tree.strict){
|
||||
if(item[optgroup] && (tree.show && tree.strict || cascader.show && cascader.strict)){
|
||||
let child = item[children], change = [], isAdd = true, handlerType;
|
||||
if(item.__node.selected){
|
||||
handlerType = 'del';
|
||||
@ -280,7 +294,7 @@ class Framework extends Component{
|
||||
this.treeHandler(sels, item, change, handlerType);
|
||||
}
|
||||
|
||||
if(this.checkMax(change, change)){
|
||||
if(this.checkMax(change, sels)){//TODO 这里还是有问题, 如果是取消的
|
||||
return ;
|
||||
}
|
||||
sels = [ ...this.state.sels ], change = [];
|
||||
@ -308,6 +322,7 @@ class Framework extends Component{
|
||||
}else{
|
||||
sels = [...sels, item]
|
||||
}
|
||||
this.clearAndReset(data, sels, itemSelected)
|
||||
this.resetSelectValue(sels, [item], !itemSelected);
|
||||
}
|
||||
}
|
||||
@ -387,7 +402,7 @@ class Framework extends Component{
|
||||
}else
|
||||
//树状结构数据更新
|
||||
if(type === 'treeData'){
|
||||
this.value(data, null, true)
|
||||
this.value(data, null, true, false, false)
|
||||
}else
|
||||
//树状结构数据更新
|
||||
if(type === 'close'){
|
||||
@ -399,7 +414,7 @@ class Framework extends Component{
|
||||
}else
|
||||
//聚焦label搜索框
|
||||
if(type === 'labelSearchBlur'){
|
||||
this.labelRef.blur(data);
|
||||
this.labelRef && this.labelRef.blur(data);
|
||||
}else
|
||||
//聚焦label搜索框
|
||||
if(type === 'labelSearch'){
|
||||
@ -422,7 +437,7 @@ class Framework extends Component{
|
||||
sels.splice(index, 1);
|
||||
}
|
||||
});
|
||||
this.value(sels, this.props.show, true, changeData)
|
||||
this.value(sels, this.props.show, true, changeData, false)
|
||||
}
|
||||
|
||||
auto(arr){
|
||||
@ -439,6 +454,21 @@ class Framework extends Component{
|
||||
}
|
||||
}
|
||||
|
||||
calcPosition(){
|
||||
if(!this.base || !this.state.show){
|
||||
return {}
|
||||
}
|
||||
let rect = this.base.getBoundingClientRect();
|
||||
(Date.now() - this.state.time > 10) && this.setState({ time: Date.now() })
|
||||
return {
|
||||
position: 'fixed',
|
||||
left: rect.x,
|
||||
top: rect.y + rect.height + 4,
|
||||
width: rect.width,
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
//组件将要接收新属性
|
||||
componentWillReceiveProps(props){
|
||||
this.init(props, props.updateData);
|
||||
@ -450,7 +480,7 @@ class Framework extends Component{
|
||||
}
|
||||
|
||||
render(config, state) {
|
||||
const { theme, prop, radio, repeat, clickClose, on, max, maxMethod, content, disabled, tree } = config;
|
||||
const { theme, prop, radio, repeat, clickClose, on, max, maxMethod, content, disabled, tree, submitConversion } = config;
|
||||
const borderStyle = { borderColor: theme.color };
|
||||
let { data, dataObj, flatData, sels, show, tmpColor, bodyClass } = state;
|
||||
|
||||
@ -482,6 +512,7 @@ class Framework extends Component{
|
||||
|
||||
//渲染组件
|
||||
let Body = content ? <Custom { ...bodyProps } /> : tree.show ? <Tree { ...bodyProps } ref={ ref => this.treeRef = ref } /> : config.cascader.show ? <Cascader { ...bodyProps } /> : <General { ...bodyProps } ref={ ref => this.generalRef = ref } />;
|
||||
let bodyStyle = this.calcPosition();
|
||||
|
||||
return (
|
||||
<xm-select { ...xmSelectProps } >
|
||||
@ -490,12 +521,12 @@ class Framework extends Component{
|
||||
lay-verType={ config.layVerType }
|
||||
lay-reqText={ config.layReqText }
|
||||
name={ config.name }
|
||||
value={ sels.map(item => item[prop.value]).join(',') }
|
||||
value={ submitConversion(sels, prop) }
|
||||
></input>
|
||||
<i class={ show ? 'xm-icon xm-icon-expand' : 'xm-icon' } />
|
||||
{ sels.length === 0 && <div class="xm-tips">{ config.tips }</div> }
|
||||
<Label { ...labelProps } ref={ ref => this.labelRef = ref } />
|
||||
<div class={ ['xm-body', bodyClass, config.model.type, show ? '':'dis', ].join(' ') } ref={ ref => this.bodyView = ref}>
|
||||
<div class={ ['xm-body', bodyClass, config.model.type, show ? '':'dis', ].join(' ') } style={ bodyStyle } ref={ ref => this.bodyView = ref}>
|
||||
{ Body }
|
||||
</div>
|
||||
{ disabled && <div class="xm-select-disabled"></div> }
|
||||
@ -510,10 +541,15 @@ class Framework extends Component{
|
||||
//监听键盘事件
|
||||
this.base.addEventListener('keydown', e => {
|
||||
let keyCode = e.keyCode;
|
||||
//ENTER
|
||||
if(keyCode === 13){
|
||||
this.onClick()
|
||||
this.onClick(e)
|
||||
}
|
||||
});
|
||||
|
||||
// focus 可以监听tab切换
|
||||
// this.base.addEventListener('focus', e => {
|
||||
// })
|
||||
|
||||
//表单验证
|
||||
this.input = this.base.querySelector('.xm-select-default');
|
||||
@ -528,7 +564,7 @@ class Framework extends Component{
|
||||
this.input.className = 'xm-select-default';
|
||||
this.base.style.borderColor = this.props.theme.maxColor;
|
||||
//这里可以自己新增一个回调, 也许看到源码的你能够看到
|
||||
// this.base.scrollIntoView({ behavior: "smooth" });
|
||||
this.base.scrollIntoView && this.base.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -548,6 +584,8 @@ class Framework extends Component{
|
||||
dom = dom.parentElement;
|
||||
}
|
||||
|
||||
let { done } = this.props;
|
||||
done && done();
|
||||
}
|
||||
|
||||
//此时页面又被重新渲染了
|
||||
@ -558,7 +596,12 @@ class Framework extends Component{
|
||||
return ;
|
||||
}
|
||||
|
||||
if(model.type === 'fixed'){
|
||||
return ;
|
||||
}
|
||||
|
||||
let rect = this.base.getBoundingClientRect();
|
||||
let bodyViewHeight;
|
||||
if(direction === 'auto'){
|
||||
//用于控制js获取下拉框的高度
|
||||
this.bodyView.style.display = 'block';
|
||||
@ -566,7 +609,7 @@ class Framework extends Component{
|
||||
|
||||
//获取下拉元素的高度
|
||||
let bodyViewRect = this.bodyView.getBoundingClientRect();
|
||||
let bodyViewHeight = bodyViewRect.height;
|
||||
bodyViewHeight = bodyViewRect.height;
|
||||
|
||||
//还原控制效果
|
||||
this.bodyView.style.display = '';
|
||||
@ -578,13 +621,12 @@ class Framework extends Component{
|
||||
let diff = clientHeight - y - rect.height - 20;
|
||||
direction = diff > bodyViewHeight || y < diff ? 'down' : 'up';
|
||||
}
|
||||
|
||||
if(direction == 'down'){
|
||||
this.bodyView.style.top = rect.height + 4 + 'px';
|
||||
this.bodyView.style.top = (rect.height + 4 + rect.y )+ 'px';
|
||||
this.bodyView.style.bottom = 'auto';
|
||||
}else{
|
||||
this.bodyView.style.top = 'auto';
|
||||
this.bodyView.style.bottom = rect.height + 4 + 'px';
|
||||
this.bodyView.style.top = (4 +rect.y - bodyViewHeight )+ 'px';
|
||||
this.bodyView.style.bottom = 'auto';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,65 @@ class Label extends Component{
|
||||
let input = this.base.querySelector('.label-search-input');
|
||||
input && input.blur();
|
||||
}
|
||||
|
||||
labelDrag(item, e){
|
||||
let type = e.type;
|
||||
let node = e.target;
|
||||
while(true){
|
||||
if(!node || node.tagName === 'I'){
|
||||
return ;
|
||||
}
|
||||
if(node.tagName === 'DIV' && node.style.position !== 'fixed'){
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
console.log(e)
|
||||
|
||||
if(type === 'mousedown'){
|
||||
let dragNode = node.cloneNode(true);
|
||||
let { pageX, pageY, offsetX, offsetY } = e;//鼠标当前位置
|
||||
|
||||
console.log(pageX, pageY, offsetX, offsetY)
|
||||
|
||||
dragNode.style.position = 'fixed';
|
||||
dragNode.style.left = (pageX - offsetX) + 'px';
|
||||
dragNode.style.top = (pageY - offsetY) + 'px';
|
||||
node.appendChild(dragNode);
|
||||
|
||||
console.log(dragNode)
|
||||
|
||||
|
||||
dragNode.onmousemove = (ev) => {
|
||||
dragNode.style.left = (ev.pageX - offsetX) + 'px';
|
||||
dragNode.style.top = (ev.pageY - offsetY) + 'px';
|
||||
}
|
||||
|
||||
dragNode.mouseup = () => {
|
||||
dragNode.parentNode.removeChild(dragNode);
|
||||
dragNode.onmousemove = null;
|
||||
dragNode.mouseup = null;
|
||||
dragNode.mouseleave = null;
|
||||
}
|
||||
dragNode.mouseleave = () => {
|
||||
console.log('mouseleave')
|
||||
}
|
||||
}else if(type === 'mouseup'){
|
||||
let childs = node.childNodes;
|
||||
for(let i = 0; i < childs.length; i++) {
|
||||
let f = childs[i];
|
||||
if(f.tagName === 'DIV'){
|
||||
node.removeChild(f);
|
||||
f.onmousemove = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if (this.labelRef.addEventListener) {
|
||||
@ -81,9 +140,13 @@ class Label extends Component{
|
||||
|
||||
html = arr.splice(0, count).map(sel => {
|
||||
const styleProps = { width: conf.showIcon ? 'calc(100% - 20px)' : '100%', }
|
||||
const className = ['xm-label-block', sel[disabled] ? 'disabled':''].join(' ');
|
||||
const className = ['xm-label-block', sel[disabled] ? 'disabled':''].join(' ');
|
||||
// onMouseDown = { this.labelDrag.bind(this, sel) }
|
||||
// onMouseUp = { this.labelDrag.bind(this, sel) }
|
||||
return (
|
||||
<div class={className} style={ style }>
|
||||
<div class={className} style={ style }
|
||||
|
||||
>
|
||||
{ conf.template && isFunction(conf.template) ? (
|
||||
<span style={ styleProps } dangerouslySetInnerHTML={{ __html: conf.template(sel, arr) }}></span>
|
||||
) : (
|
||||
@ -104,7 +167,10 @@ class Label extends Component{
|
||||
}
|
||||
}else if(type == 'search'){
|
||||
innerHTML = false;
|
||||
let one = list[0][name];
|
||||
let one = '';
|
||||
if(list.length){
|
||||
one = list[0][name]
|
||||
}
|
||||
|
||||
html = (
|
||||
<input class="label-search-input" type="text" placeholder={ config.searchTips } style={{ width: '100%', border: 'none' }} value={
|
||||
|
@ -18,7 +18,7 @@ class Cascader extends Component{
|
||||
|
||||
optionClick(item, selected, disabled, type, index, e){
|
||||
if(type === 'line'){
|
||||
if(disabled){
|
||||
if(!item.optgroup && disabled){
|
||||
return ;
|
||||
}
|
||||
//加载中的不需要进行处理
|
||||
@ -57,7 +57,7 @@ class Cascader extends Component{
|
||||
render(config, state) {
|
||||
|
||||
const { prop, empty, sels, theme, radio, template, data, cascader } = config;
|
||||
let { name, value, disabled, children } = prop;
|
||||
let { name, value, disabled, children, optgroup } = prop;
|
||||
const showIcon = config.model.icon != 'hidden';
|
||||
|
||||
const renderItem = (item, indent, index, checked) => {
|
||||
@ -108,6 +108,12 @@ class Cascader extends Component{
|
||||
itemStyle.backgroundColor = theme.hover
|
||||
}
|
||||
|
||||
//隐藏图标的处理
|
||||
if(!showIcon && selected){
|
||||
itemStyle.backgroundColor = theme.color;
|
||||
dis && (itemStyle.backgroundColor = '#C2C2C2');
|
||||
}
|
||||
|
||||
const contentStyle = {}, checkedStyle = {};
|
||||
if(checked){
|
||||
contentStyle.color = theme.color
|
||||
@ -133,7 +139,7 @@ class Cascader extends Component{
|
||||
} onMouseEnter={ hoverChange } onMouseLeave={ hoverChange }>
|
||||
{ showIcon && <i class={ iconClass } style={ iconStyle } onClick={ this.optionClick.bind(this, item, selected, dis, 'checkbox', index) }></i> }
|
||||
<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> }
|
||||
{ item[optgroup] && <div class={ checkedClass } style={ checkedStyle }></div> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -146,7 +152,7 @@ class Cascader extends Component{
|
||||
const checked = child && this.state.expand[index] === item[value];
|
||||
checked && boxArr.push(
|
||||
<div class="xm-cascader-box" index={ index % 4 } style={{ left: indent + 'px', width: cascader.indent + 'px'}}>
|
||||
<div class="xm-cascader-scroll">{ child.map(c => renderGroup(c, indent, index + 1)) }</div>
|
||||
<div class="xm-cascader-scroll scroll-body">{ child.map(c => renderGroup(c, indent, index + 1)) }</div>
|
||||
</div>
|
||||
)
|
||||
return renderItem(item, indent, index, checked)
|
||||
@ -161,7 +167,7 @@ class Cascader extends Component{
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={ this.blockClick } class="xm-body-cascader" style={{ width: cascader.indent + 'px', maxHeight: config.height }}>
|
||||
<div onClick={ this.blockClick } class="xm-body-cascader scroll-body" style={{ width: cascader.indent + 'px', maxHeight: config.height }}>
|
||||
{ arr }
|
||||
</div>
|
||||
)
|
||||
|
@ -87,7 +87,7 @@ class General extends Component{
|
||||
}
|
||||
}
|
||||
|
||||
searchInput(e){
|
||||
searchInput(e){
|
||||
let v = e.target.value;
|
||||
|
||||
if(v === this.__value){
|
||||
@ -115,7 +115,7 @@ class General extends Component{
|
||||
this.searchInputRef && this.searchInputRef.blur();
|
||||
}
|
||||
|
||||
handleComposition(e){
|
||||
handleComposition(e){
|
||||
let type = e.type;
|
||||
|
||||
if(type === 'compositionstart'){
|
||||
@ -164,7 +164,7 @@ class General extends Component{
|
||||
this.pageNextClick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(this.props.enableKeyboard){
|
||||
const { value, optgroup, disabled } = this.props.prop;
|
||||
let data = this.tempData.filter(item => !item[optgroup] && !item[disabled]);
|
||||
@ -172,7 +172,7 @@ class General extends Component{
|
||||
if(len === -1){
|
||||
return ;
|
||||
}
|
||||
|
||||
|
||||
let index = data.findIndex(item => item[value] === this.state.val);
|
||||
//Up 键
|
||||
if(keyCode === 38){
|
||||
@ -183,9 +183,7 @@ class General extends Component{
|
||||
}
|
||||
let val = data[index][value];
|
||||
this.setState({ val })
|
||||
//键盘选中时滚动到可视范围内
|
||||
let opt = this.base.querySelector(`.xm-option[value="${ val }"]`);
|
||||
opt && opt.scrollIntoView(false)
|
||||
this.viewTo(val);
|
||||
}else
|
||||
//Down 键
|
||||
if(keyCode === 40){
|
||||
@ -196,19 +194,24 @@ class General extends Component{
|
||||
}
|
||||
let val = data[index][value];
|
||||
this.setState({ val })
|
||||
//键盘选中时滚动到可视范围内
|
||||
let opt = this.base.querySelector(`.xm-option[value="${ val }"]`);
|
||||
opt && opt.scrollIntoView(false)
|
||||
this.viewTo(val);
|
||||
}else
|
||||
//Enter 键
|
||||
if(keyCode === 13){
|
||||
if(keyCode === this.props.selectedKeyCode){
|
||||
if(this.state.val != emptyVal){
|
||||
let option = data[index];
|
||||
this.optionClick(option, this.props.sels.findIndex(item => item[value] === this.state.val) != -1, option[disabled], e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewTo(val){
|
||||
//键盘选中时滚动到可视范围内
|
||||
if(void 0 != this.base){
|
||||
let opt = this.base.querySelector(`.xm-option[value="${ val }"]`);
|
||||
opt && opt.scrollIntoView(false)
|
||||
}
|
||||
}
|
||||
|
||||
//组件将要接收新属性
|
||||
@ -239,7 +242,7 @@ class General extends Component{
|
||||
}
|
||||
|
||||
render(config) {
|
||||
let { data, flatData, prop, template, theme, radio, sels, empty, filterable, filterMethod, remoteSearch, remoteMethod, delay, searchTips, create, pageRemote, max, enableKeyboard } = config
|
||||
let { data, flatData, prop, template, theme, radio, sels, empty, filterable, filterMethod, remoteSearch, remoteMethod, delay, searchTips, create, pageRemote, max, enableKeyboard, enableHoverFirst } = config
|
||||
|
||||
const { name, value, disabled, children, optgroup } = prop;
|
||||
|
||||
@ -286,8 +289,9 @@ class General extends Component{
|
||||
|
||||
//如果是分组模式, 要分页先去除分组, 然后在计算分页, 最后再加上分组
|
||||
let groupInfo = {};
|
||||
arr.filter(item => item[optgroup]).forEach(g => {
|
||||
g[children].forEach(item => groupInfo[item[value]] = g);
|
||||
arr.filter(item => item[optgroup]).forEach((g, groupIndex) => {
|
||||
groupInfo[groupIndex] = g;
|
||||
g[children].forEach(item => item.__group__index = groupIndex);
|
||||
});
|
||||
arr = arr.filter(item => !item[optgroup]);
|
||||
|
||||
@ -342,7 +346,8 @@ class General extends Component{
|
||||
let newArr = [], group, tmpGroup = { __tmp: true };
|
||||
tmpGroup[optgroup] = true;
|
||||
arr.forEach(item => {
|
||||
let g = groupInfo[item[value]];
|
||||
let g = groupInfo[item.__group__index];
|
||||
delete item.__group__index;
|
||||
if(group && !g){
|
||||
g = tmpGroup
|
||||
}
|
||||
@ -356,8 +361,10 @@ class General extends Component{
|
||||
|
||||
//查看是否创建了条目
|
||||
if(creator){
|
||||
creator = create(this.state.filterValue, deepMerge([], arr));
|
||||
creator && arr.splice(0, 0, {...creator, __node: {}});
|
||||
creator = create(this.state.filterValue, deepMerge([], arr));
|
||||
if(void 0 != creator){
|
||||
arr.splice(0, 0, ...(isArray(creator) ? creator : [creator]).map(i => ({ ...i, __node: {} })));
|
||||
}
|
||||
}
|
||||
|
||||
let safetyArr = deepMerge([], arr);
|
||||
@ -519,10 +526,14 @@ class General extends Component{
|
||||
|
||||
arr = arr.map(renderGroup);
|
||||
|
||||
if(!arr.length){
|
||||
if(arr.length){
|
||||
if(enableHoverFirst && this.state.val == emptyVal){
|
||||
this.keydown('div', { keyCode: 40 })
|
||||
}
|
||||
}else{
|
||||
//查看无数据情况下是否显示分页
|
||||
!config.pageEmptyShow && (paging = '');
|
||||
arr.push(<div class="xm-select-empty">{ empty }</div>)
|
||||
arr.push(<div class="xm-select-empty">{ empty }</div>)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -20,6 +20,7 @@ class Tree extends Component{
|
||||
this.inputOver = true;
|
||||
this.__value = '';
|
||||
this.tempData = [];
|
||||
this.__skipAutoExpand = '';
|
||||
}
|
||||
|
||||
init(props){
|
||||
@ -59,7 +60,7 @@ class Tree extends Component{
|
||||
|
||||
const { tree, prop, sels } = this.props;
|
||||
const { clickExpand, clickCheck } = tree;
|
||||
|
||||
|
||||
//检测点击的是不是三角箭头
|
||||
let isExpand = e.target && isFunction(e.target.getAttribute) && e.target.getAttribute('type') === 'expand'
|
||||
//如果点击即展开
|
||||
@ -180,10 +181,13 @@ class Tree extends Component{
|
||||
}
|
||||
|
||||
if(!hiddenStatus){//如果是显示状态
|
||||
let keys = this.state.expandedKeys;
|
||||
if(val && keys.findIndex(key => key === item[value]) === -1){
|
||||
keys.push(item[value]);
|
||||
this.setState({ expandedKeys: keys })
|
||||
if(this.__skipAutoExpand != val){//第一次搜索默认展开过滤后的数据
|
||||
let keys = this.state.expandedKeys;
|
||||
if(val && keys.findIndex(key => key === item[value]) === -1){
|
||||
keys.push(item[value]);
|
||||
this.setState({ expandedKeys: keys })
|
||||
}
|
||||
this.__skipAutoExpand = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,6 +220,7 @@ class Tree extends Component{
|
||||
//清空输入框的值
|
||||
this.setState({ filterValue: '', val: emptyVal });
|
||||
this.__value = '';
|
||||
this.__skipAutoExpand = '';
|
||||
this.searchInputRef && (this.searchInputRef.value = '');
|
||||
}else{
|
||||
//聚焦输入框
|
||||
|
@ -25,7 +25,8 @@ class xmOptions {
|
||||
let updateData = !!options.data;
|
||||
|
||||
//记录最新的配置项
|
||||
this.options = deepMerge(this.options, options);
|
||||
this.options = deepMerge(this.options, options);
|
||||
this.options.__render_success = false;
|
||||
|
||||
//如果dom不存在, 则不进行渲染事项
|
||||
let { dom } = this.options;
|
||||
@ -45,7 +46,9 @@ class xmOptions {
|
||||
}
|
||||
|
||||
render(<Framework { ...this.options } __update={ Date.now() } updateData={ updateData } />, dom);
|
||||
|
||||
|
||||
this.options.__render_success = true;
|
||||
|
||||
//返回多选对象
|
||||
return this;
|
||||
}
|
||||
@ -213,7 +216,7 @@ class xmOptions {
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 动态操作树状结构的节点展开状态
|
||||
*/
|
||||
@ -250,6 +253,23 @@ class xmOptions {
|
||||
}
|
||||
childData[this.options.el].upDate(sels, false)
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到某个选项
|
||||
*/
|
||||
scroll(val){
|
||||
let opt = this.options.dom.querySelector(`.xm-option[value="${ val }"]`);
|
||||
opt && opt.scrollIntoView(false)
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算下拉的位置
|
||||
*/
|
||||
calcPosition(){
|
||||
childData[this.options.el].calcPosition()
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -80,7 +80,11 @@ export default function (lan = 'zn') {
|
||||
//选项显示数量
|
||||
showCount: 0,
|
||||
//是否开启键盘操作
|
||||
enableKeyboard: true,
|
||||
enableKeyboard: true,
|
||||
//开启键盘操作后是否默认选中第一条
|
||||
enableHoverFirst: true,
|
||||
//键盘选中的KeyCode, 默认是Enter
|
||||
selectedKeyCode: 13,
|
||||
//工具条
|
||||
toolbar: {
|
||||
show: false,
|
||||
@ -160,7 +164,7 @@ export default function (lan = 'zn') {
|
||||
},
|
||||
},
|
||||
icon: 'show',
|
||||
type: 'absolute',
|
||||
type: 'absolute',//可选值, relative, fixed
|
||||
},
|
||||
//自定义选中的图标
|
||||
iconfont: {
|
||||
@ -184,6 +188,12 @@ export default function (lan = 'zn') {
|
||||
//监听选中事件
|
||||
on({ arr, item, selected }){
|
||||
|
||||
}
|
||||
},
|
||||
submitConversion(sels, prop){
|
||||
return sels.map(item => item[prop.value]).join(',')
|
||||
},
|
||||
done(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
110
src/index.js
110
src/index.js
@ -1,15 +1,20 @@
|
||||
import { name, version } from '../package.json'
|
||||
import { selector, warn } from '@/common/util'
|
||||
import Select from '@/components/xm-select'
|
||||
import { name, version, website } from '../package.json'
|
||||
import { selector, warn } from '@/common/util'
|
||||
import Select from '@/components/xm-select'
|
||||
|
||||
export const datas = {};
|
||||
export const optionData = {};
|
||||
export const childData = {};
|
||||
|
||||
export default {
|
||||
name,
|
||||
export default {
|
||||
name,
|
||||
version,
|
||||
render(options) {
|
||||
doc: website,
|
||||
KeyCode: {
|
||||
Enter: 13,
|
||||
Space: 32,
|
||||
},
|
||||
render(options) {
|
||||
let { el } = options;
|
||||
options.dom = selector(el);
|
||||
|
||||
@ -20,52 +25,59 @@ export default {
|
||||
options.el = el;
|
||||
}
|
||||
optionData[el] = options;
|
||||
|
||||
|
||||
let instance = new Select(options);
|
||||
//已经渲染
|
||||
if (instance) {
|
||||
//已经渲染
|
||||
if (instance && instance.options.__render_success) {
|
||||
datas[el] = instance;
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
get(filter, single) {
|
||||
let type = Object.prototype.toString.call(filter);
|
||||
let method;
|
||||
switch (type) {
|
||||
case '[object String]':
|
||||
filter && (method = item => item === filter);
|
||||
break;
|
||||
case '[object RegExp]':
|
||||
method = item => filter.test(item);
|
||||
break;
|
||||
case '[object Function]':
|
||||
method = filter;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let keys = Object.keys(datas)
|
||||
let list = (method ? keys.filter(method) : keys).map(key => datas[key]).filter(instance => selector(instance.options.el));
|
||||
|
||||
return single ? list[0] : list;
|
||||
},
|
||||
batch(filter, method) {
|
||||
let args = [...arguments];
|
||||
args.splice(0, 2);
|
||||
return this.get(filter).map(instance => instance[method](...args));
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
arr2tree(arr, pid, id, children, topParentId){
|
||||
arr.forEach(item => {
|
||||
if(item[pid] != topParentId){
|
||||
let parent = arr.find(i => i[id] === item[pid])
|
||||
if(parent){
|
||||
if(!parent[children]){
|
||||
parent[children] = [];
|
||||
}
|
||||
parent[children].push(item)
|
||||
}
|
||||
get(filter, single) {
|
||||
let type = Object.prototype.toString.call(filter);
|
||||
let method;
|
||||
switch (type) {
|
||||
case '[object String]':
|
||||
filter && (method = item => item === filter);
|
||||
break;
|
||||
case '[object RegExp]':
|
||||
method = item => filter.test(item);
|
||||
break;
|
||||
case '[object Function]':
|
||||
method = filter;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let keys = Object.keys(datas)
|
||||
let list = (method ? keys.filter(method) : keys).map(key => datas[key]).filter(instance => selector(instance.options.el));
|
||||
|
||||
return single ? list[0] : list;
|
||||
},
|
||||
batch(filter, method) {
|
||||
let args = [...arguments];
|
||||
args.splice(0, 2);
|
||||
return this.get(filter).map(instance => instance[method](...args));
|
||||
},
|
||||
arr2tree(arr=[], pid='pid', id='id', children='children', topParentId=0){
|
||||
const datas = {} // 数据缓存
|
||||
return safety(arr).filter(item =>{
|
||||
const _id = item[id] // 提取自身id 必须唯一
|
||||
const _pid = item[pid] // 提取父级id
|
||||
const self = datas[_id] // 读取数据缓存中的数据
|
||||
let parent = datas[_pid] // 读取父级数据
|
||||
if(self){ // 如果缓存中有数据
|
||||
item[children] = self[children] // 把缓存的数据保存到自身
|
||||
}
|
||||
datas[_id] = item // 替换缓存中的数据
|
||||
if(!parent){ // 如果没有找到父级数据
|
||||
parent = { // 创建一个全新的父级数据
|
||||
[children]:[]
|
||||
}
|
||||
datas[_pid] = parent // 记录到缓存
|
||||
}
|
||||
parent.push(item) // 推送自身到父级
|
||||
return id == topParentId // 这里用 == 防止出现 字符串不等于数字类型的情况
|
||||
})
|
||||
return arr.filter(i => i[pid] == topParentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
41
src/main.js
41
src/main.js
@ -1,8 +1,9 @@
|
||||
import '@/common/expand'
|
||||
import '@/style/index.less'
|
||||
import '@/style/iconfont.less'
|
||||
import { default as xmSelect, datas } from './index.js';
|
||||
|
||||
import '@/style/iconfont.less'
|
||||
import '@/style/index.less'
|
||||
import { default as xmSelect, datas } from './index.js';
|
||||
import {throttle} from '@/common/util'
|
||||
|
||||
const moduleName = 'xmSelect';
|
||||
|
||||
/**
|
||||
@ -14,16 +15,26 @@ window.addEventListener('click', () => {
|
||||
item && item.closed && item.closed()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
|
||||
module.exports = xmSelect;
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
define(xmSelect);
|
||||
} else if (window.layui && layui.define) {
|
||||
layui.define(function(exports) {
|
||||
exports(moduleName, xmSelect);
|
||||
});
|
||||
|
||||
/**
|
||||
* 监听页面滚动事件
|
||||
*/
|
||||
window.addEventListener('scroll', throttle(() => {
|
||||
Object.keys(datas).forEach(key => {
|
||||
let item = datas[key]
|
||||
item && item.calcPosition && item.calcPosition()
|
||||
})
|
||||
}))
|
||||
|
||||
|
||||
if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
|
||||
module.exports = xmSelect;
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
define(xmSelect);
|
||||
} else if (window.layui && layui.define) {
|
||||
layui.define(function(exports) {
|
||||
exports(moduleName, xmSelect);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
window[moduleName] = xmSelect;
|
||||
|
@ -61,7 +61,7 @@ xm-select{
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
&:hover{
|
||||
&:hover,&:focus{
|
||||
border-color: #C0C4CC;
|
||||
}
|
||||
|
||||
@ -109,11 +109,13 @@ xm-select{
|
||||
}
|
||||
.label-content{
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
&.auto-row{
|
||||
.label-content{
|
||||
flex-wrap: wrap;
|
||||
padding-right: 30px !important;
|
||||
}
|
||||
.xm-label-block > span{
|
||||
white-space: unset;
|
||||
@ -124,7 +126,7 @@ xm-select{
|
||||
.scroll{
|
||||
.label-content{
|
||||
display: flex;
|
||||
padding: 3px 30px 3px 10px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,6 +488,9 @@ xm-select{
|
||||
.disabled .xm-right-arrow{
|
||||
color: #C2C2C2 !important;
|
||||
}
|
||||
.hide-icon.disabled .xm-right-arrow{
|
||||
color: #999 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user