mini-select 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// app.js
|
|
2
|
+
App({
|
|
3
|
+
onLaunch() {
|
|
4
|
+
// 展示本地存储能力
|
|
5
|
+
const logs = wx.getStorageSync('logs') || []
|
|
6
|
+
logs.unshift(Date.now())
|
|
7
|
+
wx.setStorageSync('logs', logs)
|
|
8
|
+
|
|
9
|
+
// 登录
|
|
10
|
+
wx.login({
|
|
11
|
+
success: res => {
|
|
12
|
+
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
},
|
|
16
|
+
globalData: {
|
|
17
|
+
userInfo: null
|
|
18
|
+
}
|
|
19
|
+
})
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Component({
|
|
2
|
+
properties: {
|
|
3
|
+
value: {
|
|
4
|
+
type: [String, Number],
|
|
5
|
+
value: null
|
|
6
|
+
},
|
|
7
|
+
options: {
|
|
8
|
+
type: Array,
|
|
9
|
+
value: []
|
|
10
|
+
},
|
|
11
|
+
valueKey: {
|
|
12
|
+
type: String,
|
|
13
|
+
value: 'value'
|
|
14
|
+
},
|
|
15
|
+
labelKey: {
|
|
16
|
+
type: String,
|
|
17
|
+
value: 'label'
|
|
18
|
+
},
|
|
19
|
+
placeholder: {
|
|
20
|
+
type: String,
|
|
21
|
+
value: '请选择'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
data: {
|
|
26
|
+
open: false,
|
|
27
|
+
selectedLabel: '',
|
|
28
|
+
keyword: '',
|
|
29
|
+
inputValue: '',
|
|
30
|
+
normalizedOptions: [],
|
|
31
|
+
filteredOptions: []
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
observers: {
|
|
35
|
+
'value, options, valueKey, labelKey'(value, options, valueKey, labelKey) {
|
|
36
|
+
const safeOptions = Array.isArray(options) ? options : []
|
|
37
|
+
const normalizedOptions = this.normalizeOptions(safeOptions, valueKey, labelKey)
|
|
38
|
+
const current = normalizedOptions.find(item => item.value === value)
|
|
39
|
+
const selectedLabel = current ? current.label : ''
|
|
40
|
+
const keyword = this.data.keyword || ''
|
|
41
|
+
const filteredOptions = this.getFilteredOptions(keyword, normalizedOptions)
|
|
42
|
+
|
|
43
|
+
if (this.data.open) {
|
|
44
|
+
this.setData({
|
|
45
|
+
selectedLabel,
|
|
46
|
+
normalizedOptions,
|
|
47
|
+
filteredOptions
|
|
48
|
+
})
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.setData({
|
|
53
|
+
selectedLabel,
|
|
54
|
+
keyword: '',
|
|
55
|
+
inputValue: selectedLabel,
|
|
56
|
+
normalizedOptions,
|
|
57
|
+
filteredOptions
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
lifetimes: {
|
|
63
|
+
attached() {
|
|
64
|
+
const normalizedOptions = this.normalizeOptions(
|
|
65
|
+
this.data.options,
|
|
66
|
+
this.data.valueKey,
|
|
67
|
+
this.data.labelKey
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
this.setData({
|
|
71
|
+
normalizedOptions,
|
|
72
|
+
filteredOptions: this.getFilteredOptions('', normalizedOptions),
|
|
73
|
+
inputValue: this.data.selectedLabel || ''
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
methods: {
|
|
79
|
+
normalizeOptions(options, valueKey, labelKey) {
|
|
80
|
+
const safeOptions = Array.isArray(options) ? options : []
|
|
81
|
+
const vk = valueKey || 'value'
|
|
82
|
+
const lk = labelKey || 'label'
|
|
83
|
+
|
|
84
|
+
return safeOptions.map(optionItem => {
|
|
85
|
+
const raw = optionItem
|
|
86
|
+
const value = raw && vk in raw ? raw[vk] : undefined
|
|
87
|
+
const label = raw && lk in raw ? raw[lk] : ''
|
|
88
|
+
return { value, label, raw }
|
|
89
|
+
})
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
getFilteredOptions(keyword, options) {
|
|
93
|
+
const safeOptions = Array.isArray(options) ? options : []
|
|
94
|
+
const k = String(keyword || '').trim().toLowerCase()
|
|
95
|
+
if (!k) return safeOptions
|
|
96
|
+
|
|
97
|
+
return safeOptions.filter(item =>
|
|
98
|
+
String(item && item.label != null ? item.label : '')
|
|
99
|
+
.toLowerCase()
|
|
100
|
+
.includes(k)
|
|
101
|
+
)
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
toggle() {
|
|
105
|
+
const nextOpen = !this.data.open
|
|
106
|
+
if (nextOpen) {
|
|
107
|
+
this.setData({
|
|
108
|
+
open: true,
|
|
109
|
+
filteredOptions: this.getFilteredOptions(this.data.keyword, this.data.normalizedOptions)
|
|
110
|
+
})
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.setData({
|
|
115
|
+
open: false,
|
|
116
|
+
keyword: '',
|
|
117
|
+
inputValue: this.data.selectedLabel || '',
|
|
118
|
+
filteredOptions: this.getFilteredOptions('', this.data.normalizedOptions)
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
onFocus() {
|
|
123
|
+
this.setData({
|
|
124
|
+
open: true,
|
|
125
|
+
filteredOptions: this.getFilteredOptions(this.data.keyword, this.data.normalizedOptions)
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
onInput(e) {
|
|
130
|
+
const keyword = e.detail && typeof e.detail.value === 'string' ? e.detail.value : ''
|
|
131
|
+
this.setData({
|
|
132
|
+
open: true,
|
|
133
|
+
keyword,
|
|
134
|
+
inputValue: keyword,
|
|
135
|
+
filteredOptions: this.getFilteredOptions(keyword, this.data.normalizedOptions)
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
select(e) {
|
|
140
|
+
const { value, label } = e.currentTarget.dataset
|
|
141
|
+
const selectedItem = this.data.normalizedOptions.find(item => item.value === value)
|
|
142
|
+
this.setData({
|
|
143
|
+
open: false,
|
|
144
|
+
selectedLabel: label,
|
|
145
|
+
keyword: '',
|
|
146
|
+
inputValue: label,
|
|
147
|
+
filteredOptions: this.getFilteredOptions('', this.data.normalizedOptions)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
this.triggerEvent('change', { value, label, item: selectedItem ? selectedItem.raw : undefined })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<view class="select">
|
|
2
|
+
<view class="select-trigger">
|
|
3
|
+
<input
|
|
4
|
+
class="select-input"
|
|
5
|
+
placeholder="{{ placeholder }}"
|
|
6
|
+
value="{{ inputValue }}"
|
|
7
|
+
bindfocus="onFocus"
|
|
8
|
+
bindinput="onInput"
|
|
9
|
+
/>
|
|
10
|
+
<text class="arrow" bindtap="toggle">{{ open ? '▲' : '▼' }}</text>
|
|
11
|
+
</view>
|
|
12
|
+
|
|
13
|
+
<view wx:if="{{ open && filteredOptions.length }}" class="options">
|
|
14
|
+
<view class="options-list">
|
|
15
|
+
<view
|
|
16
|
+
wx:for="{{ filteredOptions }}"
|
|
17
|
+
wx:key="value"
|
|
18
|
+
class="option"
|
|
19
|
+
bindtap="select"
|
|
20
|
+
data-value="{{ item.value }}"
|
|
21
|
+
data-label="{{ item.label }}"
|
|
22
|
+
>
|
|
23
|
+
{{ item.label }}
|
|
24
|
+
</view>
|
|
25
|
+
</view>
|
|
26
|
+
</view>
|
|
27
|
+
</view>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
.select {
|
|
2
|
+
position: relative;
|
|
3
|
+
font-size: 28rpx;
|
|
4
|
+
margin: 10rpx;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.select-trigger {
|
|
8
|
+
padding: 20rpx;
|
|
9
|
+
border: 1px solid #ddd;
|
|
10
|
+
border-radius: 8rpx;
|
|
11
|
+
border-radius: 10rpx;
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
align-items: center;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.select-input {
|
|
18
|
+
flex: 1;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
height: 40rpx;
|
|
21
|
+
line-height: 40rpx;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.options {
|
|
25
|
+
position: absolute;
|
|
26
|
+
width: 100%;
|
|
27
|
+
background: #fff;
|
|
28
|
+
border: 1px solid #ddd;
|
|
29
|
+
border-radius: 8rpx;
|
|
30
|
+
margin-top: 20rpx;
|
|
31
|
+
z-index: 10;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.options-list {
|
|
35
|
+
max-height: 400rpx;
|
|
36
|
+
overflow-y: auto;
|
|
37
|
+
-webkit-overflow-scrolling: touch;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* 边框三角 */
|
|
41
|
+
.options::before {
|
|
42
|
+
content: '';
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: -16rpx;
|
|
45
|
+
left: 50%;
|
|
46
|
+
transform: translateX(-50%);
|
|
47
|
+
border-width: 0 16rpx 16rpx 16rpx;
|
|
48
|
+
border-style: solid;
|
|
49
|
+
border-color: transparent transparent #ddd transparent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* 白色三角(覆盖在边框上) */
|
|
53
|
+
.options::after {
|
|
54
|
+
content: '';
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: -14rpx;
|
|
57
|
+
left: 50%;
|
|
58
|
+
transform: translateX(-50%);
|
|
59
|
+
border-width: 0 14rpx 14rpx 14rpx;
|
|
60
|
+
border-style: solid;
|
|
61
|
+
border-color: transparent transparent #fff transparent;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.option {
|
|
65
|
+
padding: 20rpx;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.option:hover {
|
|
69
|
+
background-color: #f5f5f5;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.arrow {
|
|
73
|
+
color: #999;
|
|
74
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mini-select",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "微信小程序自定义Select组件",
|
|
5
|
+
"miniprogram": "components",
|
|
6
|
+
"files": ["components"],
|
|
7
|
+
"keywords": ["miniprogram", "wechat", "select"],
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "Jean",
|
|
10
|
+
"type": "commonjs",
|
|
11
|
+
"main": "app.js",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
}
|
|
15
|
+
}
|