stellar-ui-plus 1.24.26 → 1.25.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/components/ste-app-update/method.ts +1 -0
- package/components/ste-app-update/props.ts +11 -11
- package/components/ste-app-update/ste-app-update.vue +2 -7
- package/components/ste-radio/README.md +1 -1
- package/components/ste-radio/ste-radio.vue +2 -7
- package/components/ste-radio-group/ste-radio-group.vue +2 -1
- package/components/ste-select-seat/ATTRIBUTES.md +18 -0
- package/components/ste-select-seat/README.md +280 -0
- package/components/ste-select-seat/canvasUtils.ts +42 -0
- package/components/ste-select-seat/config.json +5 -0
- package/components/ste-select-seat/internals/gridUtils.ts +23 -0
- package/components/ste-select-seat/internals/seatLayout.ts +169 -0
- package/components/ste-select-seat/internals/useSeatInteraction.ts +540 -0
- package/components/ste-select-seat/props.ts +37 -0
- package/components/ste-select-seat/ste-select-seat.easycom.json +62 -0
- package/components/ste-select-seat/ste-select-seat.vue +517 -0
- package/components/ste-select-seat/types.d.ts +33 -0
- package/components/ste-select-seat/useData.ts +179 -0
- package/components/ste-select-seat/useTouchCompat.ts +89 -0
- package/components/ste-simple-calendar/ATTRIBUTES.md +17 -0
- package/components/ste-simple-calendar/README.md +112 -0
- package/components/ste-simple-calendar/config.json +5 -0
- package/components/ste-simple-calendar/props.ts +32 -0
- package/components/ste-simple-calendar/ste-simple-calendar.easycom.json +60 -0
- package/components/ste-simple-calendar/ste-simple-calendar.vue +265 -0
- package/components/ste-simple-calendar/type.d.ts +30 -0
- package/components/ste-simple-calendar/useData.ts +60 -0
- package/components/ste-skeleton/ATTRIBUTES.md +7 -0
- package/components/ste-skeleton/README.md +82 -0
- package/components/ste-skeleton/config.json +5 -0
- package/components/ste-skeleton/props.ts +7 -0
- package/components/ste-skeleton/ste-skeleton.easycom.json +38 -0
- package/components/ste-skeleton/ste-skeleton.vue +90 -0
- package/components/ste-slide-verify/ATTRIBUTES.md +27 -0
- package/components/ste-slide-verify/README.md +118 -0
- package/components/ste-slide-verify/config.json +5 -0
- package/components/ste-slide-verify/props.ts +43 -0
- package/components/ste-slide-verify/ste-slide-verify.easycom.json +119 -0
- package/components/ste-slide-verify/ste-slide-verify.vue +535 -0
- package/index.ts +8 -0
- package/package.json +1 -1
- package/types/components.d.ts +8 -0
- package/types/index.d.ts +2 -0
- package/types/refComponents.d.ts +8 -0
|
@@ -16,7 +16,7 @@ export default {
|
|
|
16
16
|
default: '',
|
|
17
17
|
validator: (value: string) => {
|
|
18
18
|
return typeof value === 'string' && value.length > 0;
|
|
19
|
-
}
|
|
19
|
+
},
|
|
20
20
|
},
|
|
21
21
|
clientSecret: {
|
|
22
22
|
type: String,
|
|
@@ -24,7 +24,7 @@ export default {
|
|
|
24
24
|
default: '',
|
|
25
25
|
validator: (value: string) => {
|
|
26
26
|
return typeof value === 'string' && value.length > 0;
|
|
27
|
-
}
|
|
27
|
+
},
|
|
28
28
|
},
|
|
29
29
|
apiUrl: {
|
|
30
30
|
type: String,
|
|
@@ -34,37 +34,37 @@ export default {
|
|
|
34
34
|
try {
|
|
35
35
|
new URL(value);
|
|
36
36
|
return true;
|
|
37
|
-
} catch {
|
|
37
|
+
} catch (e) {
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
41
|
},
|
|
42
42
|
appType: {
|
|
43
43
|
type: String,
|
|
44
44
|
default: '',
|
|
45
45
|
validator: (value: string) => {
|
|
46
46
|
return typeof value === 'string';
|
|
47
|
-
}
|
|
47
|
+
},
|
|
48
48
|
},
|
|
49
49
|
btnText: {
|
|
50
50
|
type: String,
|
|
51
51
|
default: '立即体验',
|
|
52
52
|
validator: (value: string) => {
|
|
53
53
|
return typeof value === 'string' && value.length > 0;
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
55
|
},
|
|
56
56
|
appVersion: {
|
|
57
57
|
type: String,
|
|
58
58
|
default: '',
|
|
59
59
|
validator: (value: string) => {
|
|
60
60
|
return typeof value === 'string';
|
|
61
|
-
}
|
|
61
|
+
},
|
|
62
62
|
},
|
|
63
63
|
zIndex: {
|
|
64
64
|
type: [Number, String],
|
|
65
65
|
default: () => 998,
|
|
66
66
|
validator: (value: any) => {
|
|
67
|
-
return typeof value === 'number' || typeof value === 'string'
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
} satisfies Record<keyof AppUpdateProps, any>;
|
|
67
|
+
return typeof value === 'number' || typeof value === 'string';
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
} satisfies Record<keyof AppUpdateProps, any>;
|
|
@@ -33,7 +33,6 @@ const packageFileSize = ref('0');
|
|
|
33
33
|
const tempFilePath = ref('');
|
|
34
34
|
|
|
35
35
|
// 资源管理
|
|
36
|
-
let downloadTask: UniApp.DownloadTask | null = null;
|
|
37
36
|
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
38
37
|
|
|
39
38
|
// 跳过版本相关
|
|
@@ -90,10 +89,6 @@ const skipVersion = () => {
|
|
|
90
89
|
|
|
91
90
|
// 清理函数
|
|
92
91
|
const cleanup = () => {
|
|
93
|
-
if (downloadTask) {
|
|
94
|
-
downloadTask.abort();
|
|
95
|
-
downloadTask = null;
|
|
96
|
-
}
|
|
97
92
|
if (timeoutTimer) {
|
|
98
93
|
clearTimeout(timeoutTimer);
|
|
99
94
|
timeoutTimer = null;
|
|
@@ -244,7 +239,7 @@ const confirm = () => {
|
|
|
244
239
|
if (data.package_type == 0) {
|
|
245
240
|
if (data.updateFile.includes('.apk')) {
|
|
246
241
|
updateBtn.value = false;
|
|
247
|
-
|
|
242
|
+
downloadMethod(data, {
|
|
248
243
|
onProgressUpdate,
|
|
249
244
|
downloadSuccess: path => (tempFilePath.value = path),
|
|
250
245
|
error: () => {
|
|
@@ -263,7 +258,7 @@ const confirm = () => {
|
|
|
263
258
|
}
|
|
264
259
|
} else {
|
|
265
260
|
updateBtn.value = false;
|
|
266
|
-
|
|
261
|
+
downloadMethod(data, {
|
|
267
262
|
onProgressUpdate,
|
|
268
263
|
downloadSuccess: path => (tempFilePath.value = path),
|
|
269
264
|
error: () => {
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
| 参数 | 说明 | 类型 | 默认值 | 可选值 | 支持版本 |
|
|
250
250
|
| ------------------- | --------------------------------- | --------------- | --------- | --------------------------------- | --------- |
|
|
251
251
|
| `value` | 当前选中值(支持v-model双向绑定) | `String` | `` | - | - |
|
|
252
|
-
| `direction` | 排列方式 | `String` | `
|
|
252
|
+
| `direction` | 排列方式 | `String` | `column` | `row`:横向 <br/>`column`:纵向 | - |
|
|
253
253
|
| `disabled` | 是否禁用 | `Boolean` | `false` | - | - |
|
|
254
254
|
| `readonly` | 只读 (不置灰) | `Boolean` | `false` | - | - |
|
|
255
255
|
| `shape` | 形状 | `String` | `circle` | `circle`:圆形 <br/>`squar`:方形 | - |
|
|
@@ -44,6 +44,7 @@ const cmpRootStyle = computed(() => {
|
|
|
44
44
|
flexDirection: textPosition === 'right' ? 'row' : 'row-reverse',
|
|
45
45
|
marginLeft: utils.formatPx(getDefaultData('marginLeft', '0')),
|
|
46
46
|
marginRight: utils.formatPx(getDefaultData('marginRight', '0')),
|
|
47
|
+
columnGap: utils.formatPx(getDefaultData('columnGap', '16')),
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
// #ifdef H5
|
|
@@ -62,12 +63,6 @@ const cmpRootStyle = computed(() => {
|
|
|
62
63
|
return style;
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
const cmpIconStyle = computed(() => {
|
|
66
|
-
return {
|
|
67
|
-
marginRight: utils.formatPx(getDefaultData('columnGap', '16')),
|
|
68
|
-
} as CSSProperties;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
66
|
const cmpInputStyle = computed(() => {
|
|
72
67
|
const shape = getDefaultData('shape', 'circle');
|
|
73
68
|
const iconSize = getDefaultData('iconSize', 36);
|
|
@@ -162,7 +157,7 @@ const getDefaultData = <T,>(key: PropsKeyType, defaultValue: T): T => {
|
|
|
162
157
|
|
|
163
158
|
<template>
|
|
164
159
|
<view class="ste-radio-root" :style="[cmpRootStyle]" @click="click">
|
|
165
|
-
<view class="icon"
|
|
160
|
+
<view class="icon">
|
|
166
161
|
<slot name="icon" :slotProps="cmpSlotProps">
|
|
167
162
|
<view class="input-icon" :style="[cmpInputStyle]">
|
|
168
163
|
<ste-icon v-if="cmpChecked" :size="cmpIconProps.size" code="" :color="cmpIconProps.color" bold></ste-icon>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#### Props
|
|
2
|
+
| 属性名 | 说明 | 类型 | 默认值 | 可选值 | 支持版本 |
|
|
3
|
+
| ----- | ----- | --- | ------- | ------ | -------- |
|
|
4
|
+
| `modelValue` | 已选座位坐标列表(仅包含 row/col) | `SteSelectSeatValue[]` | - | - | - |
|
|
5
|
+
| `rows` | 行数 | `number` | - | - | - |
|
|
6
|
+
| `cols` | 列数 | `number` | - | - | - |
|
|
7
|
+
| `width` | 组件宽度(px) | `number` | `350` | - | - |
|
|
8
|
+
| `height` | 组件高度(px) | `number` | `400` | - | - |
|
|
9
|
+
| `seats` | 座位属性配置(未配置的位置会自动补齐为默认座位) | `SteSelectSeatItem[]` | - | - | - |
|
|
10
|
+
| `seatSize` | 座位尺寸(rpx) | `number` | `40` | - | - |
|
|
11
|
+
| `seatGap` | 座位间距(rpx) | `number` | `8` | - | - |
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#### Events
|
|
15
|
+
| 事件名 | 说明 | 事件参数 | 支持版本 |
|
|
16
|
+
| ----- | ----- | ------- | -------- |
|
|
17
|
+
| `seat-click` | 点击有效座位事件(empty/disabled 不触发) | - | - |
|
|
18
|
+
| `move` | 拖动/缩放事件 | - | - |
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# SelectSeat 座位选择
|
|
2
|
+
|
|
3
|
+
用于电影院、演出、车厢选座等场景的座位选择组件,基于 Canvas 渲染,支持单指拖拽、双指缩放,适合中大规模座位图展示。
|
|
4
|
+
|
|
5
|
+
---$
|
|
6
|
+
|
|
7
|
+
## 代码演示
|
|
8
|
+
|
|
9
|
+
### 基础用法
|
|
10
|
+
|
|
11
|
+
最简单的用法只需要传入行列数和画布尺寸。
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<template>
|
|
15
|
+
<ste-select-seat v-model="selected" :rows="5" :cols="8" :width="340" :height="240" />
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script lang="ts" setup>
|
|
19
|
+
import { ref } from 'vue';
|
|
20
|
+
import type { SteSelectSeatValue } from 'stellar-ui-plus/components/ste-select-seat/types';
|
|
21
|
+
|
|
22
|
+
const selected = ref<SteSelectSeatValue[]>([]);
|
|
23
|
+
</script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 自定义座位
|
|
27
|
+
|
|
28
|
+
通过 `seats` 可以覆盖指定坐标的座位属性,常见场景包括:
|
|
29
|
+
|
|
30
|
+
- `empty: true` 表示该位置留空,不渲染座位
|
|
31
|
+
- `disabled: true` 表示该位置不可选
|
|
32
|
+
- 也可以单独配置某个区域的颜色
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<template>
|
|
36
|
+
<ste-select-seat v-model="selected" :rows="5" :cols="8" :width="340" :height="240" :seats="customSeats" selectedBgColor="#007aff" />
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script lang="ts" setup>
|
|
40
|
+
import { ref } from 'vue';
|
|
41
|
+
import type { SteSelectSeatItem, SteSelectSeatValue } from 'stellar-ui-plus/components/ste-select-seat/types';
|
|
42
|
+
|
|
43
|
+
const selected = ref<SteSelectSeatValue[]>([]);
|
|
44
|
+
|
|
45
|
+
const customSeats: SteSelectSeatItem[] = [
|
|
46
|
+
{ row: 0, col: 2, empty: true },
|
|
47
|
+
{ row: 0, col: 3, empty: true },
|
|
48
|
+
{ row: 1, col: 0, disabled: true },
|
|
49
|
+
{ row: 1, col: 1, disabled: true },
|
|
50
|
+
{ row: 2, col: 4, bgColor: '#fff3e0', borderColor: '#ff9800', selectedBgColor: '#ff9800' },
|
|
51
|
+
{ row: 2, col: 5, bgColor: '#fff3e0', borderColor: '#ff9800', selectedBgColor: '#ff9800' },
|
|
52
|
+
{ row: 4, col: 0, empty: true },
|
|
53
|
+
{ row: 4, col: 7, empty: true },
|
|
54
|
+
];
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 预选座位
|
|
59
|
+
|
|
60
|
+
预选本质上就是给 `v-model` 初始值。这里的坐标只表示“已选状态”,不负责定义座位是否禁用、是否留空。
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<template>
|
|
64
|
+
<ste-select-seat v-model="selected" :rows="5" :cols="8" :width="340" :height="240" />
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script lang="ts" setup>
|
|
68
|
+
import { ref } from 'vue';
|
|
69
|
+
import type { SteSelectSeatValue } from 'stellar-ui-plus/components/ste-select-seat/types';
|
|
70
|
+
|
|
71
|
+
const selected = ref<SteSelectSeatValue[]>([
|
|
72
|
+
{ row: 1, col: 3 },
|
|
73
|
+
{ row: 1, col: 4 },
|
|
74
|
+
{ row: 2, col: 3 },
|
|
75
|
+
{ row: 2, col: 4 },
|
|
76
|
+
]);
|
|
77
|
+
</script>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 重置位置
|
|
81
|
+
|
|
82
|
+
组件实例暴露了 `reset()`,用于把当前缩放和拖拽视口恢复到初始状态,不会清空已选座位。
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<template>
|
|
86
|
+
<ste-select-seat ref="seatRef" v-model="selected" :rows="5" :cols="8" :width="340" :height="240" />
|
|
87
|
+
<button @click="reset">重置位置</button>
|
|
88
|
+
</template>
|
|
89
|
+
|
|
90
|
+
<script lang="ts" setup>
|
|
91
|
+
import { ref } from 'vue';
|
|
92
|
+
import type { SteSelectSeatValue } from 'stellar-ui-plus/components/ste-select-seat/types';
|
|
93
|
+
|
|
94
|
+
const seatRef = ref<any>(null);
|
|
95
|
+
const selected = ref<SteSelectSeatValue[]>([]);
|
|
96
|
+
|
|
97
|
+
const reset = () => {
|
|
98
|
+
seatRef.value?.reset();
|
|
99
|
+
};
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 电影院座位图
|
|
104
|
+
|
|
105
|
+
复杂场景通常会同时用到以下能力:
|
|
106
|
+
|
|
107
|
+
- 通过 `seats` 区分已售、优选区、情侣座
|
|
108
|
+
- 通过 `empty` 留出过道、边角空位
|
|
109
|
+
- 通过 `move` 事件同步顶部银幕等浮层位置
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<script lang="ts" setup>
|
|
113
|
+
import { computed, ref } from 'vue';
|
|
114
|
+
import type { SteSelectSeatItem, SteSelectSeatMoveEvent, SteSelectSeatValue } from 'stellar-ui-plus/components/ste-select-seat/types';
|
|
115
|
+
|
|
116
|
+
type SeatCoord = { row: number; col: number };
|
|
117
|
+
|
|
118
|
+
const rows = 12;
|
|
119
|
+
const cols = 20;
|
|
120
|
+
const selected = ref<SteSelectSeatValue[]>([
|
|
121
|
+
{ row: 6, col: 7 },
|
|
122
|
+
{ row: 6, col: 8 },
|
|
123
|
+
]);
|
|
124
|
+
const viewport = ref<SteSelectSeatMoveEvent>({
|
|
125
|
+
translateX: 0,
|
|
126
|
+
translateY: 0,
|
|
127
|
+
scale: 1,
|
|
128
|
+
screenTranslateX: 0,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const soldSeatCoords: SeatCoord[] = [
|
|
132
|
+
{ row: 2, col: 3 },
|
|
133
|
+
{ row: 2, col: 4 },
|
|
134
|
+
{ row: 4, col: 6 },
|
|
135
|
+
{ row: 7, col: 14 },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const vipSeatCoords: SeatCoord[] = [
|
|
139
|
+
{ row: 6, col: 6 },
|
|
140
|
+
{ row: 6, col: 7 },
|
|
141
|
+
{ row: 6, col: 8 },
|
|
142
|
+
{ row: 6, col: 11 },
|
|
143
|
+
{ row: 6, col: 12 },
|
|
144
|
+
{ row: 6, col: 13 },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const coupleSeatCoords: SeatCoord[] = [
|
|
148
|
+
{ row: 10, col: 5 },
|
|
149
|
+
{ row: 10, col: 6 },
|
|
150
|
+
{ row: 10, col: 13 },
|
|
151
|
+
{ row: 10, col: 14 },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
const toSeatKey = ({ row, col }: SeatCoord) => `${row}-${col}`;
|
|
155
|
+
const soldSeatSet = new Set(soldSeatCoords.map(toSeatKey));
|
|
156
|
+
const vipSeatSet = new Set(vipSeatCoords.map(toSeatKey));
|
|
157
|
+
const coupleSeatSet = new Set(coupleSeatCoords.map(toSeatKey));
|
|
158
|
+
|
|
159
|
+
const isAisleCol = (col: number) => col === 9 || col === 10;
|
|
160
|
+
const isFrontSideGap = (row: number, col: number) => (row === 0 || row === 1) && (col <= 1 || col >= 18);
|
|
161
|
+
const isBackCornerGap = (row: number, col: number) => row === 11 && (col === 0 || col === 19);
|
|
162
|
+
const isEmptySeat = (row: number, col: number) => isAisleCol(col) || isFrontSideGap(row, col) || isBackCornerGap(row, col);
|
|
163
|
+
|
|
164
|
+
const seats = computed<SteSelectSeatItem[]>(() => {
|
|
165
|
+
const list: SteSelectSeatItem[] = [];
|
|
166
|
+
|
|
167
|
+
for (let row = 0; row < rows; row++) {
|
|
168
|
+
for (let col = 0; col < cols; col++) {
|
|
169
|
+
if (isEmptySeat(row, col)) {
|
|
170
|
+
list.push({ row, col, empty: true });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const key = `${row}-${col}`;
|
|
175
|
+
|
|
176
|
+
if (soldSeatSet.has(key)) {
|
|
177
|
+
list.push({ row, col, disabled: true, bgColor: '#cfd4dc', borderColor: '#cfd4dc' });
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (vipSeatSet.has(key)) {
|
|
182
|
+
list.push({ row, col, bgColor: '#fff4d6', borderColor: '#ffb400', selectedBgColor: '#ffb400' });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (coupleSeatSet.has(key)) {
|
|
187
|
+
list.push({ row, col, bgColor: '#ffe4ea', borderColor: '#ff7a9d', selectedBgColor: '#ff5c8a' });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return list;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const onMove = (event: SteSelectSeatMoveEvent) => {
|
|
197
|
+
viewport.value = event;
|
|
198
|
+
};
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<template>
|
|
202
|
+
<view class="seat-stage">
|
|
203
|
+
<view class="screen" :style="{ transform: `translateX(${viewport.screenTranslateX}px)` }">银幕中央</view>
|
|
204
|
+
|
|
205
|
+
<ste-select-seat
|
|
206
|
+
v-model="selected"
|
|
207
|
+
:rows="rows"
|
|
208
|
+
:cols="cols"
|
|
209
|
+
:width="340"
|
|
210
|
+
:height="200"
|
|
211
|
+
:seat-size="30"
|
|
212
|
+
:seat-gap="6"
|
|
213
|
+
:border-radius="8"
|
|
214
|
+
:seats="seats"
|
|
215
|
+
selected-bg-color="#2d6cdf"
|
|
216
|
+
@move="onMove"
|
|
217
|
+
/>
|
|
218
|
+
</view>
|
|
219
|
+
</template>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---$
|
|
223
|
+
|
|
224
|
+
## API
|
|
225
|
+
|
|
226
|
+
<!-- props -->
|
|
227
|
+
|
|
228
|
+
### 组件方法(Methods)
|
|
229
|
+
|
|
230
|
+
通过组件实例 `ref` 可调用以下方法:
|
|
231
|
+
|
|
232
|
+
| 方法名 | 说明 | 参数 |
|
|
233
|
+
| ---------- | ------------------------------------------------ | -------------------------------------------------------------- |
|
|
234
|
+
| `setSeat` | 设置指定座位属性,不直接控制选中状态 | `(row: number, col: number, data: Partial<SteSelectSeatItem>)` |
|
|
235
|
+
| `getSeats` | 获取内部当前全量座位数据,包含自动补齐的普通座位 | - |
|
|
236
|
+
| `reset` | 重置当前缩放和拖拽位置,不清空 `modelValue` | - |
|
|
237
|
+
|
|
238
|
+
`setSeat()` 常用于接口返回后,动态更新某个座位的可售状态或颜色。
|
|
239
|
+
|
|
240
|
+
## 数据说明
|
|
241
|
+
|
|
242
|
+
### 坐标约定
|
|
243
|
+
|
|
244
|
+
- 所有 `row`、`col` 均从 `0` 开始计数
|
|
245
|
+
- `row: 0, col: 0` 表示第一行第一列
|
|
246
|
+
- 页面展示给用户时,通常会自行转成 `row + 1`、`col + 1`
|
|
247
|
+
|
|
248
|
+
### `modelValue` 与 `seats` 的区别
|
|
249
|
+
|
|
250
|
+
- `modelValue` 只表示当前选中了哪些座位
|
|
251
|
+
- `seats` 只表示这些座位是什么属性
|
|
252
|
+
- 两者职责分离,避免把“选中状态”和“座位配置”混在一起
|
|
253
|
+
|
|
254
|
+
例如:
|
|
255
|
+
|
|
256
|
+
- 某个座位是已售:应在 `seats` 中配置 `disabled: true`
|
|
257
|
+
- 某个座位默认选中:应在 `modelValue` 中写入对应坐标
|
|
258
|
+
- 某个位置是过道:应在 `seats` 中配置 `empty: true`
|
|
259
|
+
|
|
260
|
+
## 注意事项
|
|
261
|
+
|
|
262
|
+
- `rows` 和 `cols` 必须为大于 `0` 的整数,否则不会正常渲染座位,并输出告警。
|
|
263
|
+
- `seats` 中缺少合法 `row/col`、或坐标超出 `rows/cols` 范围的项会被忽略,并输出告警。
|
|
264
|
+
- `seats` 中如果存在重复坐标,后一个配置会覆盖前一个,并输出告警。
|
|
265
|
+
- `setSeat()` 同样会校验坐标合法性,越界或非法时不会生效。
|
|
266
|
+
- `empty: true` 的位置不会渲染,也不会触发点击。
|
|
267
|
+
- `disabled: true` 的位置会渲染,但不可选、不会切换选中状态。
|
|
268
|
+
- `width`、`height` 决定可视区域大小;如果座位图较大,建议配合拖拽缩放使用。
|
|
269
|
+
- `showRowLabels` 适合影院、车厢等纵向较长的座位图场景,小型座位图可关闭以减少干扰。
|
|
270
|
+
|
|
271
|
+
## 使用建议
|
|
272
|
+
|
|
273
|
+
- 普通场景只传 `rows`、`cols`、`v-model` 即可。
|
|
274
|
+
- 有过道、不可售、分区价格时,再补充 `seats`。
|
|
275
|
+
- 如果需要“银幕跟随”“顶部浮层跟随”等效果,监听 `move` 事件即可。
|
|
276
|
+
- 如果业务里存在实时锁座、出票状态刷新,建议用 `setSeat()` 做局部更新,而不是每次重建整张座位图。
|
|
277
|
+
|
|
278
|
+
---$
|
|
279
|
+
|
|
280
|
+
{{fuyuwei}}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas 绘制工具函数(纯函数,无外部依赖)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 绘制圆角矩形路径(不填充/描边,需调用方自行 fill/stroke)
|
|
7
|
+
*/
|
|
8
|
+
export const drawRoundRect = (ctx: any, x: number, y: number, w: number, h: number, r: number) => {
|
|
9
|
+
ctx.beginPath();
|
|
10
|
+
ctx.moveTo(x + r, y);
|
|
11
|
+
ctx.lineTo(x + w - r, y);
|
|
12
|
+
ctx.arcTo(x + w, y, x + w, y + r, r);
|
|
13
|
+
ctx.lineTo(x + w, y + h - r);
|
|
14
|
+
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
|
15
|
+
ctx.lineTo(x + r, y + h);
|
|
16
|
+
ctx.arcTo(x, y + h, x, y + h - r, r);
|
|
17
|
+
ctx.lineTo(x, y + r);
|
|
18
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
19
|
+
ctx.closePath();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 在指定中心点绘制勾选标记(stroke)
|
|
24
|
+
* 视觉上尽量贴近 ste-radio 里的选中 icon,但保持纯 Canvas path,
|
|
25
|
+
* 避免依赖 iconfont 在各端 Canvas 中的字体加载能力。
|
|
26
|
+
* @param cx 中心 x
|
|
27
|
+
* @param cy 中心 y
|
|
28
|
+
* @param size 座位宽度,用于计算线宽与比例
|
|
29
|
+
* @param color 线条颜色
|
|
30
|
+
*/
|
|
31
|
+
export const drawCheck = (ctx: any, cx: number, cy: number, size: number, color: string) => {
|
|
32
|
+
ctx.beginPath();
|
|
33
|
+
ctx.strokeStyle = color;
|
|
34
|
+
ctx.lineWidth = Math.max(1.4, size * 0.1);
|
|
35
|
+
ctx.lineCap = 'round';
|
|
36
|
+
ctx.lineJoin = 'round';
|
|
37
|
+
const s = size * 0.34;
|
|
38
|
+
ctx.moveTo(cx - s * 0.62, cy - s * 0.02);
|
|
39
|
+
ctx.lineTo(cx - s * 0.14, cy + s * 0.42);
|
|
40
|
+
ctx.lineTo(cx + s * 0.62, cy - s * 0.36);
|
|
41
|
+
ctx.stroke();
|
|
42
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface SteSelectSeatGridSize {
|
|
2
|
+
rows: number
|
|
3
|
+
cols: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const isInteger = (value: unknown): value is number => {
|
|
7
|
+
return typeof value === 'number' && Number.isInteger(value)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const isPositiveInteger = (value: unknown): value is number => {
|
|
11
|
+
return isInteger(value) && value > 0
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const getSafeGridSize = (rows: number, cols: number): SteSelectSeatGridSize => {
|
|
15
|
+
return {
|
|
16
|
+
rows: isPositiveInteger(rows) ? rows : 0,
|
|
17
|
+
cols: isPositiveInteger(cols) ? cols : 0,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const isSeatInBounds = (row: number, col: number, rows: number, cols: number) => {
|
|
22
|
+
return row >= 0 && row < rows && col >= 0 && col < cols
|
|
23
|
+
}
|