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,4 @@
1
+ {
2
+ "component": true,
3
+ "styleIsolation": "isolated"
4
+ }
@@ -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
+ }