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.
Files changed (44) hide show
  1. package/components/ste-app-update/method.ts +1 -0
  2. package/components/ste-app-update/props.ts +11 -11
  3. package/components/ste-app-update/ste-app-update.vue +2 -7
  4. package/components/ste-radio/README.md +1 -1
  5. package/components/ste-radio/ste-radio.vue +2 -7
  6. package/components/ste-radio-group/ste-radio-group.vue +2 -1
  7. package/components/ste-select-seat/ATTRIBUTES.md +18 -0
  8. package/components/ste-select-seat/README.md +280 -0
  9. package/components/ste-select-seat/canvasUtils.ts +42 -0
  10. package/components/ste-select-seat/config.json +5 -0
  11. package/components/ste-select-seat/internals/gridUtils.ts +23 -0
  12. package/components/ste-select-seat/internals/seatLayout.ts +169 -0
  13. package/components/ste-select-seat/internals/useSeatInteraction.ts +540 -0
  14. package/components/ste-select-seat/props.ts +37 -0
  15. package/components/ste-select-seat/ste-select-seat.easycom.json +62 -0
  16. package/components/ste-select-seat/ste-select-seat.vue +517 -0
  17. package/components/ste-select-seat/types.d.ts +33 -0
  18. package/components/ste-select-seat/useData.ts +179 -0
  19. package/components/ste-select-seat/useTouchCompat.ts +89 -0
  20. package/components/ste-simple-calendar/ATTRIBUTES.md +17 -0
  21. package/components/ste-simple-calendar/README.md +112 -0
  22. package/components/ste-simple-calendar/config.json +5 -0
  23. package/components/ste-simple-calendar/props.ts +32 -0
  24. package/components/ste-simple-calendar/ste-simple-calendar.easycom.json +60 -0
  25. package/components/ste-simple-calendar/ste-simple-calendar.vue +265 -0
  26. package/components/ste-simple-calendar/type.d.ts +30 -0
  27. package/components/ste-simple-calendar/useData.ts +60 -0
  28. package/components/ste-skeleton/ATTRIBUTES.md +7 -0
  29. package/components/ste-skeleton/README.md +82 -0
  30. package/components/ste-skeleton/config.json +5 -0
  31. package/components/ste-skeleton/props.ts +7 -0
  32. package/components/ste-skeleton/ste-skeleton.easycom.json +38 -0
  33. package/components/ste-skeleton/ste-skeleton.vue +90 -0
  34. package/components/ste-slide-verify/ATTRIBUTES.md +27 -0
  35. package/components/ste-slide-verify/README.md +118 -0
  36. package/components/ste-slide-verify/config.json +5 -0
  37. package/components/ste-slide-verify/props.ts +43 -0
  38. package/components/ste-slide-verify/ste-slide-verify.easycom.json +119 -0
  39. package/components/ste-slide-verify/ste-slide-verify.vue +535 -0
  40. package/index.ts +8 -0
  41. package/package.json +1 -1
  42. package/types/components.d.ts +8 -0
  43. package/types/index.d.ts +2 -0
  44. package/types/refComponents.d.ts +8 -0
@@ -0,0 +1,37 @@
1
+ import type { PropType } from 'vue'
2
+ import type { SteSelectSeatItem, SteSelectSeatValue } from './types'
3
+
4
+ export default {
5
+ // 已选座位 v-model
6
+ modelValue: { type: Array as PropType<SteSelectSeatValue[]>, default: () => [] },
7
+ // 行数
8
+ rows: { type: Number, default: 0 },
9
+ // 列数
10
+ cols: { type: Number, default: 0 },
11
+ // 组件宽度(px)
12
+ width: { type: Number, default: 350 },
13
+ // 组件高度(px)
14
+ height: { type: Number, default: 400 },
15
+ // 自定义座位数据
16
+ seats: { type: Array as PropType<SteSelectSeatItem[]>, default: () => [] },
17
+ // 座位尺寸(rpx)
18
+ seatSize: { type: Number, default: 40 },
19
+ // 座位间距(rpx)
20
+ seatGap: { type: Number, default: 8 },
21
+ // 座位圆角(rpx)
22
+ borderRadius: { type: Number, default: 8 },
23
+ // 边框宽度
24
+ borderWidth: { type: Number, default: 1 },
25
+ // 座位背景色
26
+ bgColor: { type: String, default: '#ffffff' },
27
+ // 边框颜色
28
+ borderColor: { type: String, default: '#e5e5e5' },
29
+ // 选中背景色(默认用主题色)
30
+ selectedBgColor: { type: String, default: '' },
31
+ // 选中图标颜色
32
+ selectedColor: { type: String, default: '#ffffff' },
33
+ // 禁用背景色
34
+ disabledBgColor: { type: String, default: '#cccccc' },
35
+ // 显示行号
36
+ showRowLabels: { type: Boolean, default: true },
37
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "ste-select-seat",
3
+ "description": "基于 Canvas 的座位选择组件",
4
+ "example": "<ste-select-seat v-model='selected' :rows='5' :cols='10'></ste-select-seat>",
5
+ "tutorial": "https://stellar-ui.intecloud.com.cn/?projectName=stellar-ui-plus&menu=%E7%BB%84%E4%BB%B6&active=ste-select-seat",
6
+ "attributes": [
7
+ {
8
+ "name": "modelValue",
9
+ "description": "已选座位坐标列表(仅包含 row/col)",
10
+ "type": "SteSelectSeatValue[]"
11
+ },
12
+ {
13
+ "name": "rows",
14
+ "description": "行数",
15
+ "type": "number"
16
+ },
17
+ {
18
+ "name": "cols",
19
+ "description": "列数",
20
+ "type": "number"
21
+ },
22
+ {
23
+ "name": "width",
24
+ "description": "组件宽度(px)",
25
+ "type": "number",
26
+ "default": "350"
27
+ },
28
+ {
29
+ "name": "height",
30
+ "description": "组件高度(px)",
31
+ "type": "number",
32
+ "default": "400"
33
+ },
34
+ {
35
+ "name": "seats",
36
+ "description": "座位属性配置(未配置的位置会自动补齐为默认座位)",
37
+ "type": "SteSelectSeatItem[]"
38
+ },
39
+ {
40
+ "name": "seatSize",
41
+ "description": "座位尺寸(rpx)",
42
+ "type": "number",
43
+ "default": "40"
44
+ },
45
+ {
46
+ "name": "seatGap",
47
+ "description": "座位间距(rpx)",
48
+ "type": "number",
49
+ "default": "8"
50
+ },
51
+ {
52
+ "name": "[event]seat-click",
53
+ "description": "点击有效座位事件(empty/disabled 不触发)",
54
+ "type": "(seat: SteSelectSeatItem) => void"
55
+ },
56
+ {
57
+ "name": "[event]move",
58
+ "description": "拖动/缩放事件",
59
+ "type": "(data: { translateX, translateY, scale, screenTranslateX }) => void"
60
+ }
61
+ ]
62
+ }
@@ -0,0 +1,517 @@
1
+ <script setup lang="ts">
2
+ import { watch, nextTick, onMounted, getCurrentInstance, computed, ref } from 'vue';
3
+ import type { ComponentPublicInstance } from 'vue';
4
+ import propsData from './props';
5
+ import { useData } from './useData';
6
+ import { useColorStore } from '../../store/color';
7
+ import utils from '../../utils/utils';
8
+ import TouchScaleing from '../ste-media-preview/TouchScaleing';
9
+ import type { SteSelectSeatItem, SteSelectSeatValue, SteSelectSeatMoveEvent } from './types';
10
+ import { drawRoundRect, drawCheck } from './canvasUtils';
11
+ import { getSafeGridSize } from './internals/gridUtils';
12
+ import {
13
+ INTERNAL_MAX_SCALE,
14
+ buildRowLabelItems,
15
+ clampSeatScale,
16
+ getDefaultSeatViewport,
17
+ getFitScale,
18
+ getLabelWidth,
19
+ getRowLabelTrackStyle,
20
+ getScreenTranslateX as getSeatScreenTranslateX,
21
+ getSeatContentSize,
22
+ getSeatTranslateBounds,
23
+ } from './internals/seatLayout';
24
+ import { useSeatInteraction } from './internals/useSeatInteraction';
25
+ import { getTouchX, getTouchY } from './useTouchCompat';
26
+
27
+ const componentName = 'ste-select-seat';
28
+ const { getColor } = useColorStore();
29
+ const themeColor = getColor()?.steThemeColor || '#0090FF';
30
+
31
+ defineOptions({
32
+ name: componentName,
33
+ options: {
34
+ virtualHost: true,
35
+ },
36
+ });
37
+
38
+ const instance = getCurrentInstance() as unknown as ComponentPublicInstance;
39
+ const props = defineProps(propsData);
40
+ const emit = defineEmits<{
41
+ (e: 'update:modelValue', value: SteSelectSeatValue[]): void;
42
+ (e: 'seat-click', seat: SteSelectSeatItem): void;
43
+ (e: 'move', data: SteSelectSeatMoveEvent): void;
44
+ }>();
45
+
46
+ const { getSeat, setSeat, getSeats, isSelected, toggleSeat } = useData(props);
47
+
48
+ const canvasId = 'ste-select-seat-' + utils.guid(8);
49
+ const touchHandler = new TouchScaleing();
50
+
51
+ // ─── 手势状态变量 ────────────────────────────────────────────────────────────
52
+ let canvasCtx: any = null;
53
+ let dpr = 1;
54
+
55
+ // ─── 响应式视口状态 ───────────────────────────────────────────────────────────
56
+ const viewportTranslateY = ref(0);
57
+ const viewportScale = ref(1);
58
+
59
+ // ─── 尺寸 / 布局计算 ─────────────────────────────────────────────────────────
60
+ const seatSizePx = computed(() => utils.formatPx(props.seatSize, 'num') as number);
61
+ const seatGapPx = computed(() => utils.formatPx(props.seatGap, 'num') as number);
62
+ const borderRadiusPx = computed(() => utils.formatPx(props.borderRadius, 'num') as number);
63
+ const safeGrid = computed(() => getSafeGridSize(props.rows, props.cols));
64
+ const safeRows = computed(() => safeGrid.value.rows);
65
+ const safeCols = computed(() => safeGrid.value.cols);
66
+ const labelWidthPx = computed(() => getLabelWidth(props.showRowLabels, seatSizePx.value, seatGapPx.value));
67
+ const rowLabelTrackWidthPx = computed(() => 18);
68
+ const labelOverlayWidth = computed(() => `${rowLabelTrackWidthPx.value}px`);
69
+
70
+ const canvasStyle = computed(() => ({
71
+ width: `${props.width}px`,
72
+ height: `${props.height}px`,
73
+ }));
74
+
75
+ const getContentSize = () => {
76
+ return getSeatContentSize({
77
+ rows: safeRows.value,
78
+ cols: safeCols.value,
79
+ seatSize: seatSizePx.value,
80
+ seatGap: seatGapPx.value,
81
+ labelWidth: labelWidthPx.value,
82
+ });
83
+ };
84
+
85
+ const fitScale = computed(() => {
86
+ const contentSize = getContentSize();
87
+ return getFitScale({
88
+ width: props.width,
89
+ height: props.height,
90
+ contentWidth: contentSize.width,
91
+ contentHeight: contentSize.height,
92
+ });
93
+ });
94
+
95
+ const effectiveMinScale = computed(() => fitScale.value);
96
+
97
+ const clampScale = (scale: number): number => {
98
+ return clampSeatScale(scale, effectiveMinScale.value, INTERNAL_MAX_SCALE);
99
+ };
100
+
101
+ // ─── Canvas 绘制 ───────────────────────────────────────────────────────────────
102
+
103
+ const draw = () => {
104
+ const ctx = canvasCtx;
105
+ if (!ctx) return;
106
+
107
+ const userScale = clampScale(touchHandler.scale);
108
+ viewportTranslateY.value = touchHandler.translateY;
109
+ viewportScale.value = userScale;
110
+ const tx = touchHandler.translateX;
111
+ const ty = touchHandler.translateY;
112
+ const size = seatSizePx.value;
113
+ const gap = seatGapPx.value;
114
+ const radius = borderRadiusPx.value;
115
+ const labelWidth = labelWidthPx.value;
116
+ const defaultSelectedBg = props.selectedBgColor || themeColor;
117
+
118
+ ctx.clearRect(0, 0, props.width, props.height);
119
+
120
+ // #ifndef APP
121
+ ctx.save();
122
+ ctx.translate(tx * userScale, ty * userScale);
123
+ ctx.scale(userScale, userScale);
124
+ // #endif
125
+
126
+ for (let r = 0; r < safeRows.value; r++) {
127
+ for (let c = 0; c < safeCols.value; c++) {
128
+ const seat = getSeat(r, c);
129
+ if (!seat || seat.empty) continue;
130
+
131
+ const selected = isSelected(r, c);
132
+
133
+ // #ifndef APP
134
+ const x = labelWidth + c * (size + gap) + gap / 2;
135
+ const y = r * (size + gap) + gap / 2;
136
+ const w = size;
137
+ const h = size;
138
+ const r_ = radius;
139
+ // #endif
140
+ // #ifdef APP
141
+ const x = tx * userScale + (labelWidth + c * (size + gap) + gap / 2) * userScale;
142
+ const y = ty * userScale + (r * (size + gap) + gap / 2) * userScale;
143
+ const w = size * userScale;
144
+ const h = size * userScale;
145
+ const r_ = radius * userScale;
146
+ // #endif
147
+
148
+ if (seat.disabled) {
149
+ ctx.fillStyle = props.disabledBgColor;
150
+ } else if (selected) {
151
+ ctx.fillStyle = seat.selectedBgColor || defaultSelectedBg;
152
+ } else {
153
+ ctx.fillStyle = seat.bgColor || props.bgColor;
154
+ }
155
+
156
+ drawRoundRect(ctx, x, y, w, h, r_);
157
+ ctx.fill();
158
+
159
+ if (!seat.disabled && !selected) {
160
+ ctx.strokeStyle = seat.borderColor || props.borderColor;
161
+ ctx.lineWidth = props.borderWidth;
162
+ drawRoundRect(ctx, x, y, w, h, r_);
163
+ ctx.stroke();
164
+ }
165
+
166
+ if (selected && !seat.disabled) {
167
+ drawCheck(ctx, x + w / 2, y + h / 2, w, seat.selectedColor || props.selectedColor);
168
+ }
169
+ }
170
+ }
171
+
172
+ // #ifndef APP
173
+ ctx.restore();
174
+ // #endif
175
+
176
+ // #ifdef H5 || APP
177
+ if (ctx.draw) ctx.draw(true);
178
+ // #endif
179
+ };
180
+
181
+ // ─── Canvas 初始化 / 生命周期 ──────────────────────────────────────────────────
182
+
183
+ const initCanvas = () => {
184
+ nextTick(() => {
185
+ // #ifdef H5 || APP
186
+ canvasCtx = uni.createCanvasContext(canvasId, instance);
187
+ dpr = 1;
188
+ applyDefaultViewport();
189
+ draw();
190
+ // #endif
191
+
192
+ // #ifdef MP-WEIXIN || MP-ALIPAY
193
+ uni.createSelectorQuery()
194
+ .in(instance)
195
+ .select(`#${canvasId}`)
196
+ .node((res: any) => {
197
+ if (!res || !res.node) return;
198
+ const canvasNode = res.node;
199
+ dpr = utils.System.getWindowInfo().pixelRatio;
200
+ canvasNode.width = props.width * dpr;
201
+ canvasNode.height = props.height * dpr;
202
+ canvasCtx = canvasNode.getContext('2d');
203
+ canvasCtx.scale(dpr, dpr);
204
+ applyDefaultViewport();
205
+ draw();
206
+ })
207
+ .exec();
208
+ // #endif
209
+ });
210
+ };
211
+
212
+ // ─── 触摸命中检测 ────────────────────────────────────────────────────────────
213
+
214
+ const getTouchSeat = (touchX: number, touchY: number): SteSelectSeatItem | null => {
215
+ const scale = clampScale(touchHandler.scale);
216
+ const size = seatSizePx.value;
217
+ const gap = seatGapPx.value;
218
+ const labelWidth = labelWidthPx.value;
219
+ const tx = touchHandler.translateX;
220
+ const ty = touchHandler.translateY;
221
+
222
+ const col = Math.floor((touchX / scale - tx - labelWidth - gap / 2) / (size + gap));
223
+ const row = Math.floor((touchY / scale - ty - gap / 2) / (size + gap));
224
+
225
+ if (row < 0 || row >= safeRows.value || col < 0 || col >= safeCols.value) return null;
226
+ return getSeat(row, col) || null;
227
+ };
228
+
229
+ const getTouchLocalPoint = (touch: any, rect?: { left?: number; top?: number } | null) => {
230
+ if (!touch) return { x: 0, y: 0 };
231
+ if (typeof touch.x === 'number' && typeof touch.y === 'number') {
232
+ return { x: touch.x, y: touch.y };
233
+ }
234
+ return {
235
+ x: getTouchX(touch) - (rect?.left ?? 0),
236
+ y: getTouchY(touch) - (rect?.top ?? 0),
237
+ };
238
+ };
239
+
240
+ // ─── 视口变换辅助 ─────────────────────────────────────────────────────────────
241
+
242
+ const getScreenTranslateX = (scale = clampScale(touchHandler.scale), translateX = touchHandler.translateX) => {
243
+ return getSeatScreenTranslateX({
244
+ scale,
245
+ translateX,
246
+ width: props.width,
247
+ defaultViewport: getDefaultViewport(),
248
+ });
249
+ };
250
+
251
+ const emitMove = () => {
252
+ const scale = clampScale(touchHandler.scale);
253
+ emit('move', {
254
+ translateX: touchHandler.translateX,
255
+ translateY: touchHandler.translateY,
256
+ scale,
257
+ screenTranslateX: getScreenTranslateX(scale, touchHandler.translateX),
258
+ });
259
+ };
260
+
261
+ const getTranslateBounds = (scale = touchHandler.scale) => {
262
+ const contentSize = getContentSize();
263
+ return getSeatTranslateBounds({
264
+ scale: clampScale(scale),
265
+ width: props.width,
266
+ height: props.height,
267
+ contentWidth: contentSize.width,
268
+ contentHeight: contentSize.height,
269
+ });
270
+ };
271
+
272
+ const applyAxisResistance = (value: number, min: number, max: number) => {
273
+ const overscrollResistance = 0.35;
274
+ if (value < min) {
275
+ return min + (value - min) * overscrollResistance;
276
+ }
277
+ if (value > max) {
278
+ return max + (value - max) * overscrollResistance;
279
+ }
280
+ return value;
281
+ };
282
+
283
+ const applyTranslateResistance = (x: number, y: number, scale = touchHandler.scale) => {
284
+ const bounds = getTranslateBounds(scale);
285
+ return {
286
+ x: applyAxisResistance(x, bounds.minX, bounds.maxX),
287
+ y: applyAxisResistance(y, bounds.minY, bounds.maxY),
288
+ };
289
+ };
290
+
291
+ const clampTranslate = (x: number, y: number, scale = touchHandler.scale) => {
292
+ const bounds = getTranslateBounds(scale);
293
+ return {
294
+ x: Math.min(bounds.maxX, Math.max(bounds.minX, x)),
295
+ y: Math.min(bounds.maxY, Math.max(bounds.minY, y)),
296
+ };
297
+ };
298
+
299
+ const rowLabelItems = computed(() => {
300
+ if (!props.showRowLabels) return [];
301
+ return buildRowLabelItems({
302
+ rows: safeRows.value,
303
+ height: props.height,
304
+ seatSize: seatSizePx.value,
305
+ seatGap: seatGapPx.value,
306
+ translateY: viewportTranslateY.value,
307
+ scale: viewportScale.value,
308
+ });
309
+ });
310
+
311
+ const rowLabelTrackStyle = computed(() => {
312
+ return getRowLabelTrackStyle(rowLabelItems.value, props.height);
313
+ });
314
+
315
+ const getDefaultViewport = () => {
316
+ const contentSize = getContentSize();
317
+ return getDefaultSeatViewport({
318
+ fitScale: fitScale.value,
319
+ width: props.width,
320
+ height: props.height,
321
+ contentWidth: contentSize.width,
322
+ contentHeight: contentSize.height,
323
+ maxScale: INTERNAL_MAX_SCALE,
324
+ });
325
+ };
326
+
327
+ const applyDefaultViewport = () => {
328
+ const viewport = getDefaultViewport();
329
+ touchHandler.scale = viewport.scale;
330
+ touchHandler.baseScale = viewport.scale;
331
+ touchHandler.translateX = viewport.translateX;
332
+ touchHandler.translateY = viewport.translateY;
333
+ touchHandler.baseTranslateX = viewport.translateX;
334
+ touchHandler.baseTranslateY = viewport.translateY;
335
+ };
336
+
337
+ const { rowLabelsVisible, setShowRowLabelsVisible, onTouchStart, onTouchMove, onTouchEnd, onMouseDown, onMouseMove, onMouseUp, reset } = useSeatInteraction({
338
+ instance,
339
+ canvasId,
340
+ getShowRowLabels: () => props.showRowLabels,
341
+ touchHandler,
342
+ clampScale,
343
+ applyTranslateResistance,
344
+ clampTranslate,
345
+ getTouchSeat,
346
+ getTouchLocalPoint,
347
+ applyDefaultViewport,
348
+ draw,
349
+ emitMove,
350
+ emitSeatClick: seat => emit('seat-click', seat),
351
+ emitModelValue: value => emit('update:modelValue', value),
352
+ toggleSeat,
353
+ });
354
+
355
+ onMounted(() => {
356
+ initCanvas();
357
+ });
358
+
359
+ watch(
360
+ () => props.showRowLabels,
361
+ value => {
362
+ setShowRowLabelsVisible(value);
363
+ }
364
+ );
365
+
366
+ watch(
367
+ () => [
368
+ props.modelValue,
369
+ props.seats,
370
+ props.rows,
371
+ props.cols,
372
+ props.width,
373
+ props.height,
374
+ props.seatSize,
375
+ props.seatGap,
376
+ props.borderRadius,
377
+ props.borderWidth,
378
+ props.bgColor,
379
+ props.borderColor,
380
+ props.selectedBgColor,
381
+ props.selectedColor,
382
+ props.disabledBgColor,
383
+ props.showRowLabels,
384
+ ],
385
+ () => {
386
+ if (canvasCtx) draw();
387
+ },
388
+ { deep: true }
389
+ );
390
+
391
+ // ─── 对外暴露接口 ─────────────────────────────────────────────────────────────
392
+
393
+ defineExpose({
394
+ setSeat: (row: number, col: number, data: Partial<SteSelectSeatItem>) => {
395
+ setSeat(row, col, data);
396
+ nextTick(() => draw());
397
+ },
398
+ getSeats,
399
+ reset,
400
+ });
401
+ </script>
402
+
403
+ <template>
404
+ <view class="ste-select-seat-root" :style="canvasStyle">
405
+ <!-- #ifdef H5 -->
406
+ <canvas
407
+ :canvas-id="canvasId"
408
+ :id="canvasId"
409
+ :style="canvasStyle"
410
+ class="seat-canvas"
411
+ disable-scroll
412
+ @touchstart="onTouchStart"
413
+ @touchmove.stop.prevent="onTouchMove"
414
+ @touchend="onTouchEnd"
415
+ @mousedown="onMouseDown"
416
+ @mousemove="onMouseMove"
417
+ @mouseup="onMouseUp"
418
+ @mouseleave="onMouseUp"
419
+ />
420
+ <!-- #endif -->
421
+
422
+ <!-- #ifdef APP -->
423
+ <canvas
424
+ :canvas-id="canvasId"
425
+ :id="canvasId"
426
+ :style="canvasStyle"
427
+ class="seat-canvas"
428
+ disable-scroll
429
+ @touchstart="onTouchStart"
430
+ @touchmove="onTouchMove"
431
+ @touchend="onTouchEnd"
432
+ @touchcancel="onTouchEnd"
433
+ />
434
+ <!-- #endif -->
435
+
436
+ <!-- #ifdef MP-WEIXIN || MP-ALIPAY -->
437
+ <canvas
438
+ type="2d"
439
+ :id="canvasId"
440
+ :style="canvasStyle"
441
+ class="seat-canvas"
442
+ disable-scroll
443
+ @touchstart="onTouchStart"
444
+ @touchmove.stop.prevent="onTouchMove"
445
+ @touchend="onTouchEnd"
446
+ @touchcancel="onTouchEnd"
447
+ />
448
+ <!-- #endif -->
449
+
450
+ <view v-if="props.showRowLabels" class="row-label-overlay" :class="{ 'is-visible': rowLabelsVisible }">
451
+ <view class="row-label-track" :style="rowLabelTrackStyle" />
452
+ <view v-for="item in rowLabelItems" :key="item.row" class="row-label-item" :style="item.style">
453
+ {{ item.row + 1 }}
454
+ </view>
455
+ </view>
456
+ </view>
457
+ </template>
458
+
459
+ <style lang="scss" scoped>
460
+ .ste-select-seat-root {
461
+ overflow: hidden;
462
+ position: relative;
463
+ touch-action: none;
464
+ user-select: none;
465
+ }
466
+
467
+ .seat-canvas {
468
+ display: block;
469
+ }
470
+
471
+ .row-label-overlay {
472
+ position: absolute;
473
+ left: 6px;
474
+ top: 0;
475
+ bottom: 0;
476
+ width: v-bind(labelOverlayWidth);
477
+ pointer-events: none;
478
+ opacity: 0;
479
+ transform: translateX(-4px);
480
+ transition:
481
+ opacity 160ms ease,
482
+ transform 160ms ease;
483
+ }
484
+
485
+ .row-label-overlay.is-visible {
486
+ opacity: 1;
487
+ transform: translateX(0);
488
+ }
489
+
490
+ .row-label-track {
491
+ position: absolute;
492
+ left: 0;
493
+ width: 100%;
494
+ border-radius: 999px;
495
+ background: rgba(199, 199, 199, 0.68);
496
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24);
497
+ transition:
498
+ opacity 160ms ease,
499
+ transform 160ms ease;
500
+ }
501
+
502
+ .row-label-item {
503
+ position: absolute;
504
+ left: 0;
505
+ display: flex;
506
+ align-items: center;
507
+ justify-content: center;
508
+ width: 100%;
509
+ color: rgba(255, 255, 255, 0.98);
510
+ font-weight: 400;
511
+ letter-spacing: 0;
512
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
513
+ transition:
514
+ opacity 160ms ease,
515
+ transform 160ms ease;
516
+ }
517
+ </style>
@@ -0,0 +1,33 @@
1
+ /** 选中的座位值,仅表示已选中的座位坐标 */
2
+ export interface SteSelectSeatValue {
3
+ row: number
4
+ col: number
5
+ }
6
+
7
+ /** 座位项数据,用于描述座位属性,不表示选中状态 */
8
+ export interface SteSelectSeatItem {
9
+ /** 行号(从0开始) */
10
+ row: number
11
+ /** 列号(从0开始) */
12
+ col: number
13
+ /** 是否禁用 */
14
+ disabled?: boolean
15
+ /** 是否为空位(不渲染) */
16
+ empty?: boolean
17
+ /** 自定义背景色 */
18
+ bgColor?: string
19
+ /** 自定义边框颜色 */
20
+ borderColor?: string
21
+ /** 自定义选中背景色 */
22
+ selectedBgColor?: string
23
+ /** 自定义选中文字/图标颜色 */
24
+ selectedColor?: string
25
+ }
26
+
27
+ /** 移动事件参数 */
28
+ export interface SteSelectSeatMoveEvent {
29
+ translateX: number
30
+ translateY: number
31
+ scale: number
32
+ screenTranslateX: number
33
+ }