stellar-ui-plus 1.25.7 → 1.25.9

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.
@@ -177,10 +177,195 @@
177
177
  }
178
178
 
179
179
  .action-box {
180
- padding: 0 40rpx;
180
+ padding: 0 40rpx 20rpx 40rpx;
181
181
  display: flex;
182
182
  justify-content: space-between;
183
+ }
184
+ }
185
+ }
186
+ </style>
187
+ ```
188
+
189
+ ## 单据类型筛选
190
+
191
+ 此示例展示了如何实现一个单据类型筛选菜单,包含左侧分类选择、右侧输入框和子选项选择。
192
+
193
+ ```html
194
+ <script lang="ts" setup>
195
+ import type { RefDropdownMenu } from 'stellar-ui-plus/types/refComponents';
196
+ import { computed, reactive, ref } from 'vue';
197
+
198
+ const orderMenus = [
199
+ {
200
+ label: '单据类型1',
201
+ value: '1',
202
+ children: [
203
+ { label: '类型1A', value: '1-1' },
204
+ { label: '类型1B', value: '1-2' },
205
+ { label: '类型1C', value: '1-3' },
206
+ { label: '类型1D', value: '1-4' },
207
+ ],
208
+ },
209
+ {
210
+ label: '单据类型2',
211
+ value: '2',
212
+ children: [
213
+ { label: '类型2A', value: '2-1' },
214
+ { label: '类型2B', value: '2-2' },
215
+ { label: '类型2C', value: '2-3' },
216
+ { label: '类型2D', value: '2-4' },
217
+ ],
218
+ },
219
+ ];
220
+
221
+ const orderData = reactive({
222
+ parent: '1',
223
+ type: '1-1',
224
+ code: '',
225
+ });
226
+
227
+ const orderChildren = computed(() => orderMenus.find(item => item.value == orderData.parent)?.children || []);
228
+
229
+ const setOrder = (key: keyof typeof orderData, v: string) => {
230
+ orderData[key] = v;
231
+ if (key == 'parent') {
232
+ const children = orderMenus.find(item => item.value == v)?.children || [];
233
+ orderData.type = children[0]?.value || '';
234
+ }
235
+ };
236
+
237
+ const orderMenuRef = ref<RefDropdownMenu>();
238
+ function confirmOrderMenu() {
239
+ uni.showToast({
240
+ title: `确认订单 parent:${orderData.parent}, type:${orderData.type}, code:${orderData.code}`,
241
+ icon: 'none',
242
+ });
243
+ orderMenuRef.value?.close();
244
+ }
245
+ </script>
246
+ <template>
247
+ <view class="menu-item">
248
+ <view>
249
+ <ste-dropdown-menu value="2" title="单据类型" ref="orderMenuRef">
250
+ <view class="order-menu-box">
251
+ <view class="content-box">
252
+ <view class="content-left">
253
+ <view v-for="(m, i) in orderMenus" :key="i" class="left-menu-item" :class="orderData.parent == m.value ? 'active' : ''" @click="setOrder('parent', m.value)">
254
+ {{ m.label }}
255
+ </view>
256
+ </view>
257
+ <view class="content-right">
258
+ <view class="right-codes">
259
+ <view class="content-title">订单尾号</view>
260
+ <ste-input style="width: 356rpx" placeholder="请输入订单最后5位" v-model="orderData.code" background="#f5f5f5"></ste-input>
261
+ </view>
262
+ <view class="right-types">
263
+ <view class="content-title">单据类型</view>
264
+ <view class="right-menus">
265
+ <view v-for="(m, i) in orderChildren" :key="i" class="right-menu-item" :class="orderData.type == m.value ? 'active' : ''" @click="setOrder('type', m.value)">
266
+ {{ m.label }}
267
+ </view>
268
+ </view>
269
+ </view>
270
+ </view>
271
+ </view>
272
+ <view class="action-box">
273
+ <ste-button width="320" background="rgba(0,0,0,0)" borderColor="#0090FF" color="#0090FF" @click="confirmOrderMenu">重置</ste-button>
274
+ <ste-button width="320" @click="confirmOrderMenu">确认</ste-button>
275
+ </view>
276
+ </view>
277
+ </ste-dropdown-menu>
278
+ </view>
279
+ </view>
280
+ </template>
281
+ <style lang="scss">
282
+ .menu-item {
283
+ display: flex;
284
+ padding: 0 20rpx;
285
+ width: 100%;
286
+ box-shadow: 0 0 10px #ddd;
287
+ > view {
288
+ flex: 1;
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ }
293
+
294
+ .order-menu-box {
295
+ background-color: #fff;
296
+ padding-top: 24rpx;
297
+ border-top: solid 4rpx #f5f5f5;
183
298
 
299
+ .content-box {
300
+ width: 100%;
301
+ display: flex;
302
+ margin-bottom: 56rpx;
303
+ font-size: 28rpx;
304
+
305
+ .content-left {
306
+ width: 150rpx;
307
+ background-color: #f9f9f9;
308
+
309
+ .left-menu-item {
310
+ height: 90rpx;
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ font-size: 24rpx;
315
+ color: #a4a4a4;
316
+
317
+ &.active {
318
+ background-color: #fff;
319
+ color: #0090ff;
320
+ }
321
+ }
322
+ }
323
+
324
+ .content-right {
325
+ flex: 1;
326
+ margin-left: 26rpx;
327
+ margin-right: 18rpx;
328
+ background-color: #fff;
329
+
330
+ .content-title {
331
+ height: 90rpx;
332
+ display: flex;
333
+ align-items: center;
334
+ font-size: 24rpx;
335
+ color: #1c1f23;
336
+ }
337
+
338
+ .right-types {
339
+ .right-menus {
340
+ display: flex;
341
+ flex-wrap: wrap;
342
+ align-items: center;
343
+ gap: 20rpx;
344
+
345
+ .right-menu-item {
346
+ width: 162rpx;
347
+ height: 64rpx;
348
+ display: flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ background: #f9f9f9;
352
+ border-radius: 8rpx;
353
+
354
+ &.active {
355
+ background-color: #0090ff;
356
+ font-weight: bold;
357
+ color: #fff;
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ }
364
+
365
+ .action-box {
366
+ padding: 0 40rpx;
367
+ display: flex;
368
+ justify-content: space-between;
184
369
  padding-bottom: 20rpx;
185
370
  }
186
371
  }
@@ -1,14 +1,17 @@
1
1
  .ste-dropdown-menu-root {
2
2
  .menu-box {
3
3
  padding: 20rpx 0;
4
+
4
5
  .menu-title-icon {
5
6
  display: inline-flex;
6
7
  transition: all var(--duration) linear;
7
8
  }
9
+
8
10
  .title {
9
11
  font-size: var(--font-size-24, 24rpx);
10
12
  margin-right: 16rpx;
11
13
  }
14
+
12
15
  display: flex;
13
16
  align-items: center;
14
17
  }
@@ -59,6 +62,7 @@
59
62
  .menu-title-icon {
60
63
  transform: rotate(180deg);
61
64
  }
65
+
62
66
  .dropdown-content {
63
67
  opacity: 1;
64
68
  z-index: var(--menu-z-index);
@@ -73,6 +77,7 @@
73
77
  .menu-title-icon {
74
78
  transform: rotate(180deg);
75
79
  }
80
+
76
81
  .dropdown-content {
77
82
  display: flex;
78
83
  flex-direction: column;
@@ -82,10 +87,12 @@
82
87
  transform: translateY(100%);
83
88
  }
84
89
  }
90
+
85
91
  &.open {
86
92
  .menu-title-icon {
87
93
  transform: rotate(0);
88
94
  }
95
+
89
96
  .menu-item-content {
90
97
  transform: translateY(0);
91
98
  }
@@ -96,4 +103,4 @@
96
103
  }
97
104
  }
98
105
  }
99
- }
106
+ }
@@ -21,9 +21,9 @@
21
21
  ### Events
22
22
  | 事件名 | 说明 | 事件参数 | 支持版本 |
23
23
  | ----- | ----- | ------- | -------- |
24
- | `close` | 弹窗关闭动画执行完毕事件 | `data`:() => void | - |
25
- | `open` | 弹窗打开动画执行完毕事件 | `data`:() => void | - |
26
- | `maskClick` | 点击遮罩时触发 | `data`:() => void | - |
24
+ | `close` | 弹窗关闭前触发,可用于异步拦截关闭 | `suspend`:开启等待的回调函数<br/>`next`:执行后续关闭操作的回调函数<br/>`stop`:阻止后续关闭操作的回调函数 | - |
25
+ | `open-after` | 弹窗打开动画执行完毕事件 | - | - |
26
+ | `clickMask` | 点击遮罩时触发 | - | - |
27
27
 
28
28
 
29
29
  ### Slots
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed, watch, type CSSProperties } from 'vue';
2
+ import { ref, computed, watch, onUnmounted, type CSSProperties } from 'vue';
3
3
  import propsData from './props';
4
4
  import utils from '../../utils/utils';
5
5
  const DEFAULT_BORDER_RADIUS = 32;
@@ -13,12 +13,15 @@ defineOptions({
13
13
  },
14
14
  });
15
15
 
16
- const animationProp: UniApp.CreateAnimationOptions = { duration: props.duration, timingFunction: 'ease-out' };
16
+ const animationProp = computed<UniApp.CreateAnimationOptions>(() => ({ duration: props.duration, timingFunction: 'ease-out' }));
17
17
  const pageDisplay = ref('none');
18
18
  const overlayAnimationData = ref<UniApp.Animation>();
19
19
  const animationData = ref<UniApp.Animation>();
20
20
  const showContent = ref(false);
21
21
 
22
+ let beginTimer: ReturnType<typeof setTimeout> | null = null;
23
+ let endTimer: ReturnType<typeof setTimeout> | null = null;
24
+
22
25
  const emits = defineEmits<{
23
26
  (e: 'clickMask'): void;
24
27
  (e: 'close', suspend: () => void, next: () => void, stop: () => void): void;
@@ -48,14 +51,6 @@ const cmpContentStyle = computed(() => {
48
51
  height: utils.addUnit(props.height),
49
52
  backgroundColor: props.backgroundColor,
50
53
  };
51
-
52
- if (props.position === 'center') {
53
- } else if (props.position === 'bottom') {
54
- } else if (props.position === 'top') {
55
- } else if (props.position === 'left') {
56
- } else if (props.position === 'right') {
57
- }
58
-
59
54
  return style;
60
55
  });
61
56
 
@@ -108,8 +103,8 @@ async function beginAnimation() {
108
103
 
109
104
  pageDisplay.value = 'flex';
110
105
  await utils.sleep(50);
111
- let animation = uni.createAnimation(animationProp);
112
- let overlayAnimation = uni.createAnimation(animationProp);
106
+ let animation = uni.createAnimation(animationProp.value);
107
+ let overlayAnimation = uni.createAnimation(animationProp.value);
113
108
  overlayAnimation.opacity(1).step({
114
109
  duration: props.duration,
115
110
  });
@@ -128,15 +123,16 @@ async function beginAnimation() {
128
123
  overlayAnimationData.value = overlayAnimation.export();
129
124
  animationData.value = animation.export();
130
125
 
131
- setTimeout(() => {
126
+ if (beginTimer) clearTimeout(beginTimer);
127
+ beginTimer = setTimeout(() => {
132
128
  showContent.value = true;
133
129
  emits('open-after');
134
130
  }, props.duration);
135
131
  }
136
132
 
137
133
  function endAnimation() {
138
- let animation = uni.createAnimation(animationProp);
139
- let overlayAnimation = uni.createAnimation(animationProp);
134
+ let animation = uni.createAnimation(animationProp.value);
135
+ let overlayAnimation = uni.createAnimation(animationProp.value);
140
136
  overlayAnimation.opacity(0).step();
141
137
 
142
138
  if (props.position === 'center') {
@@ -150,23 +146,24 @@ function endAnimation() {
150
146
  overlayAnimationData.value = overlayAnimation.export();
151
147
  animationData.value = animation.export();
152
148
 
153
- setTimeout(() => {
149
+ if (endTimer) clearTimeout(endTimer);
150
+ endTimer = setTimeout(() => {
154
151
  pageDisplay.value = 'none';
155
152
  showContent.value = false;
156
153
  }, props.duration);
157
154
  }
158
155
 
159
- function touchmove(e: TouchEvent) {
160
- // TODO nvue 取消冒泡
161
- e.stopPropagation();
162
- }
156
+ onUnmounted(() => {
157
+ if (beginTimer) clearTimeout(beginTimer);
158
+ if (endTimer) clearTimeout(endTimer);
159
+ });
163
160
  </script>
164
161
 
165
162
  <template>
166
163
  <view class="ste-popup" :class="position" :style="[cmpPageStyle]" @click.stop="onMaskClick" :animation="overlayAnimationData" data-test="popup">
167
164
  <view class="content" :class="position" :style="[cmpContentStyle]" :animation="animationData" @click.stop>
168
165
  <template v-if="keepContent || showContent">
169
- <scroll-view style="width: 100%; height: 100%" v-if="Number(height) > 0" :scroll-y="true" @touchmove.stop.prevent="touchmove">
166
+ <scroll-view style="width: 100%; height: 100%" v-if="Number(height) > 0" :scroll-y="true" @touchmove.stop.prevent>
170
167
  <slot name="default"></slot>
171
168
  </scroll-view>
172
169
  <slot v-else name="default"></slot>
@@ -176,7 +173,7 @@ function touchmove(e: TouchEvent) {
176
173
  </view>
177
174
  </view>
178
175
  <view class="close-icon-box-center" @click="handleClose" v-if="showClose && position == 'center' && showContent">
179
- <ste-icon code="&#xe6a0;" :size="40" color="'#fff'"></ste-icon>
176
+ <ste-icon code="&#xe6a0;" :size="40" :color="'#fff'"></ste-icon>
180
177
  </view>
181
178
  </view>
182
179
  </template>
@@ -189,7 +186,6 @@ function touchmove(e: TouchEvent) {
189
186
  position: fixed;
190
187
  left: 0;
191
188
  top: 0;
192
- overflow: hidden;
193
189
  justify-content: center;
194
190
  align-items: center;
195
191
  touch-action: none;
@@ -23,7 +23,7 @@
23
23
  ### Events
24
24
  | 事件名 | 说明 | 事件参数 | 支持版本 |
25
25
  | ----- | ----- | ------- | -------- |
26
- | `click` | 点击复选框时触发的事件 | `value`:当前复选框的绑定值<br/>`suspend`:等待<br/>`next`:继续<br/>`stop`:停止 | - |
27
- | `change` | 当绑定值变化时触发的事件 | `value`:当前复选框的绑定值 | - |
26
+ | `click` | 点击单选框时触发的事件 | `value`:当前单选框的绑定值<br/>`suspend`:等待<br/>`next`:继续<br/>`stop`:停止 | - |
27
+ | `change` | 当绑定值变化时触发的事件 | `value`:当前单选框的绑定值 | - |
28
28
 
29
29
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ste-radio",
3
3
  "description": "单选框组件, 在一组备选项中进行单选。",
4
- "example": "<ste-checkbox>单选框</ste-checkbox>",
4
+ "example": "<ste-radio>单选框</ste-radio>",
5
5
  "tutorial": "https://stellar-ui.intecloud.com.cn/?projectName=stellar-ui-plus&menu=%E7%BB%84%E4%BB%B6&active=ste-radio",
6
6
  "attributes": [
7
7
  {
@@ -114,11 +114,11 @@
114
114
  },
115
115
  {
116
116
  "name": "[event]click",
117
- "description": "点击复选框时触发的事件",
117
+ "description": "点击单选框时触发的事件",
118
118
  "params": [
119
119
  {
120
120
  "name": "value",
121
- "description": "当前复选框的绑定值"
121
+ "description": "当前单选框的绑定值"
122
122
  },
123
123
  {
124
124
  "name": "suspend",
@@ -140,7 +140,7 @@
140
140
  "params": [
141
141
  {
142
142
  "name": "value",
143
- "description": "当前复选框的绑定值"
143
+ "description": "当前单选框的绑定值"
144
144
  }
145
145
  ]
146
146
  }
@@ -312,12 +312,6 @@
312
312
 
313
313
  ---$
314
314
 
315
- ## API
316
-
317
- ## Props
318
-
319
- 背景之外的颜色属性只支持`16进制`、`RGB`、`RGBA`格式
320
-
321
315
  <!-- props -->
322
316
 
323
317
  ---$
@@ -31,6 +31,13 @@ const emits = defineEmits<{
31
31
  (e: 'update:modelValue', selectedValue: string | number | number[]): void;
32
32
  }>();
33
33
 
34
+ const viewloading = ref(false);
35
+
36
+ const initOptions = (values = selectedValue.value) => {
37
+ const { options } = getDateOptions(values, props.mode as DateMode, props.minDate, props.maxDate);
38
+ setDataOptions(options);
39
+ };
40
+
34
41
  watch(
35
42
  () => props.modelValue,
36
43
  v => {
@@ -43,17 +50,27 @@ watch(
43
50
  values = value.map(item => Number(item));
44
51
  }
45
52
  setSelectValue(values as number[]);
53
+ // modelValue外部变化时,同步刷新dataOptions与selectedIndex,
54
+ // 避免打开下拉时picker仍停留在旧值位置。
55
+ viewloading.value = true;
56
+ initOptions(values);
57
+ nextTick(() => {
58
+ const indexs: number[] = [];
59
+ const _values = getNowDate(values, props.mode as DateMode);
60
+ dataOptions.value.forEach((item, index) => {
61
+ let i = item.map(({ value }) => value).indexOf(_values[index]);
62
+ if (i === -1) {
63
+ i = _values[index] > item[item.length - 1].value ? item.length - 1 : 0;
64
+ }
65
+ indexs.push(i);
66
+ });
67
+ setSelectIndex(indexs);
68
+ viewloading.value = false;
69
+ });
46
70
  },
47
71
  { immediate: true }
48
72
  );
49
73
 
50
- const initOptions = (values = selectedValue.value) => {
51
- const { options } = getDateOptions(values, props.mode as DateMode, props.minDate, props.maxDate);
52
- setDataOptions(options);
53
- };
54
-
55
- const viewloading = ref(false);
56
-
57
74
  const initSelectIndex = (values = selectedValue.value) => {
58
75
  viewloading.value = true;
59
76
  nextTick(() => {
@@ -91,9 +108,6 @@ watch([() => props.minDate, () => props.maxDate], () => {
91
108
  initOptions();
92
109
  initSelectIndex();
93
110
  });
94
-
95
- initOptions();
96
- initSelectIndex();
97
111
  </script>
98
112
  <template>
99
113
  <picker-view v-if="!viewloading" style="height: 450rpx; width: 100%" indicator-style="height: 43px" immediate-change :value="selectedIndex" @change="onChange">
@@ -0,0 +1,21 @@
1
+ ## API
2
+
3
+ ### Props
4
+ | 属性名 | 说明 | 类型 | 默认值 | 可选值 | 支持版本 |
5
+ | ----- | ----- | --- | ------- | ------ | -------- |
6
+ | `modelValue` | 绑定的值,支持v-model双向绑定 | `string / number / null / undefined` | - | - | - |
7
+ | `list` | 选项数据,每个选项支持 disabled 属性设置单项禁止选择 | `SelectOption[]` | `[]` | - | - |
8
+ | `placeholder` | 占位符 | `string` | `请选择` | - | - |
9
+ | `labelKey` | 选项标签Key | `string` | `label` | - | - |
10
+ | `valueKey` | 选项值Key | `string` | `value` | - | - |
11
+ | `disabled` | 禁用状态 | `boolean` | `false` | - | - |
12
+ | `maskClose` | 点击遮罩层是否关闭弹窗 | `boolean` | `true` | - | - |
13
+
14
+
15
+ ### Events
16
+ | 事件名 | 说明 | 事件参数 | 支持版本 |
17
+ | ----- | ----- | ------- | -------- |
18
+ | `update:modelValue` | 选中值改变时触发 | `value`:选中的值 | - |
19
+ | `change` | 选中值改变时触发 | `value`:选中的值<br/>`option`:选中的选项对象 | - |
20
+
21
+
@@ -0,0 +1,61 @@
1
+ # SelectOrder 订单选择
2
+
3
+ - 用于选择订单的下拉选择组件
4
+
5
+ ---$
6
+
7
+ ## 使用示例
8
+
9
+ ## 基础用法
10
+
11
+ - 属性`list`接收一个数组,数组中的每一项为一个对象,对象中包含`label`和`value`两个属性,分别表示节点的显示文本和节点的值
12
+ - 若`label`和`value`属性字段名不同,可通过`labelKey`和`valueKey`属性进行设置
13
+ - 属性`modelValue`接收一个值,表示当前选中的节点值,支持v-model双向绑定
14
+ - 事件`change`在节点值改变时触发,返回当前选中的值和选项对象
15
+
16
+ ```html
17
+ <template>
18
+ <ste-select-order :list="list" v-model="value" @change="onChange"></ste-select-order>
19
+ </template>
20
+ <script lang="ts" setup>
21
+ import { ref } from 'vue';
22
+ const value = ref(null);
23
+ const list = ref([
24
+ { label: '全部订单', value: 'all' },
25
+ { label: '待付款', value: 'pending' },
26
+ { label: '待发货', value: 'unshipped' },
27
+ { label: '待收货', value: 'unreceived' },
28
+ { label: '已完成', value: 'completed' },
29
+ ]);
30
+
31
+ function onChange(val, option) {
32
+ console.log(val, option);
33
+ }
34
+ </script>
35
+ ```
36
+
37
+ ## 禁用状态
38
+
39
+ - 属性`disabled`设置为`true`时,组件将被禁用
40
+
41
+ ```html
42
+ <template>
43
+ <ste-select-order :list="list" v-model="value" disabled></ste-select-order>
44
+ </template>
45
+ <script lang="ts" setup>
46
+ import { ref } from 'vue';
47
+ const value = ref(null);
48
+ const list = ref([
49
+ { label: '全部订单', value: 'all' },
50
+ { label: '待付款', value: 'pending' },
51
+ { label: '待发货', value: 'unshipped' },
52
+ ]);
53
+ </script>
54
+ ```
55
+
56
+ ---$
57
+
58
+ <!-- props -->
59
+
60
+ ---$
61
+ {{xuyajun}}
@@ -0,0 +1,5 @@
1
+ {
2
+ "group": "业务组件",
3
+ "title": "SelectOrder 订单选择",
4
+ "icon": "https://image.whzb.com/chain/StellarUI/%E7%BB%84%E4%BB%B6%E5%9B%BE%E6%A0%87/progress.png"
5
+ }
@@ -0,0 +1,30 @@
1
+ import type { PropType } from 'vue';
2
+
3
+ export interface SelectOption {
4
+ label?: string;
5
+ value: string | number;
6
+ disabled?: boolean;
7
+ [key: string]: any;
8
+ }
9
+
10
+ export type SelectValue = string | number | null | undefined;
11
+
12
+ export interface SteSelectOrderProps {
13
+ modelValue: SelectValue;
14
+ list: SelectOption[];
15
+ placeholder: string;
16
+ labelKey: string;
17
+ valueKey: string;
18
+ disabled: boolean;
19
+ maskClose: boolean;
20
+ }
21
+
22
+ export default {
23
+ modelValue: { type: [String, Number, null, undefined] as PropType<SelectValue>, default: undefined },
24
+ list: { type: Array as PropType<SelectOption[]>, default: () => [] },
25
+ placeholder: { type: String, default: '请选择' },
26
+ labelKey: { type: String, default: 'label' },
27
+ valueKey: { type: String, default: 'value' },
28
+ disabled: { type: Boolean, default: false },
29
+ maskClose: { type: Boolean, default: true },
30
+ };