stellar-ui-plus 1.24.26 → 1.24.27
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/ste-app-update.vue +2 -7
- 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/README.md +45 -0
- package/components/ste-skeleton/config.json +5 -0
- package/components/ste-skeleton/props.ts +7 -0
- package/components/ste-skeleton/ste-skeleton.json +38 -0
- package/components/ste-skeleton/ste-skeleton.vue +108 -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
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import type { ComponentPublicInstance } from 'vue'
|
|
3
|
+
import type { UniTouch, UniTouchEvent } from '../../../types/event'
|
|
4
|
+
import type { SteSelectSeatItem, SteSelectSeatValue } from '../types'
|
|
5
|
+
import { getTouchCenter, getTouchDistance, getTouchIdentifier, getTouchX, getTouchY, toTouchArray } from '../useTouchCompat'
|
|
6
|
+
|
|
7
|
+
interface UseSeatInteractionOptions {
|
|
8
|
+
instance: ComponentPublicInstance
|
|
9
|
+
canvasId: string
|
|
10
|
+
getShowRowLabels: () => boolean
|
|
11
|
+
touchHandler: {
|
|
12
|
+
scale: number
|
|
13
|
+
baseScale: number
|
|
14
|
+
translateX: number
|
|
15
|
+
translateY: number
|
|
16
|
+
baseTranslateX: number
|
|
17
|
+
baseTranslateY: number
|
|
18
|
+
reset: () => void
|
|
19
|
+
}
|
|
20
|
+
clampScale: (scale: number) => number
|
|
21
|
+
applyTranslateResistance: (x: number, y: number, scale?: number) => { x: number; y: number }
|
|
22
|
+
clampTranslate: (x: number, y: number, scale?: number) => { x: number; y: number }
|
|
23
|
+
getTouchSeat: (touchX: number, touchY: number) => SteSelectSeatItem | null
|
|
24
|
+
getTouchLocalPoint: (touch: UniTouch | undefined | null, rect?: { left?: number; top?: number } | null) => { x: number; y: number }
|
|
25
|
+
applyDefaultViewport: () => void
|
|
26
|
+
draw: () => void
|
|
27
|
+
emitMove: () => void
|
|
28
|
+
emitSeatClick: (seat: SteSelectSeatItem) => void
|
|
29
|
+
emitModelValue: (value: SteSelectSeatValue[]) => void
|
|
30
|
+
toggleSeat: (row: number, col: number) => SteSelectSeatValue[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PointerEventLike {
|
|
34
|
+
clientX: number
|
|
35
|
+
clientY: number
|
|
36
|
+
target?: EventTarget | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useSeatInteraction(options: UseSeatInteractionOptions) {
|
|
40
|
+
const {
|
|
41
|
+
instance,
|
|
42
|
+
canvasId,
|
|
43
|
+
getShowRowLabels,
|
|
44
|
+
touchHandler,
|
|
45
|
+
clampScale,
|
|
46
|
+
applyTranslateResistance,
|
|
47
|
+
clampTranslate,
|
|
48
|
+
getTouchSeat,
|
|
49
|
+
getTouchLocalPoint,
|
|
50
|
+
applyDefaultViewport,
|
|
51
|
+
draw,
|
|
52
|
+
emitMove,
|
|
53
|
+
emitSeatClick,
|
|
54
|
+
emitModelValue,
|
|
55
|
+
toggleSeat,
|
|
56
|
+
} = options
|
|
57
|
+
|
|
58
|
+
const panThreshold = 4
|
|
59
|
+
const reboundThreshold = 0.5
|
|
60
|
+
const reboundDuration = 180
|
|
61
|
+
const momentumMinVelocity = 0.02
|
|
62
|
+
const momentumDecayPerFrame = 0.92
|
|
63
|
+
|
|
64
|
+
const rowLabelsVisible = ref(getShowRowLabels())
|
|
65
|
+
const activeTouches = new Map<number | string, UniTouch>()
|
|
66
|
+
|
|
67
|
+
let dragMoved = false
|
|
68
|
+
let gestureMode: 'none' | 'pan' | 'pinch' = 'none'
|
|
69
|
+
let panStartX = 0
|
|
70
|
+
let panStartY = 0
|
|
71
|
+
let panBaseTranslateX = 0
|
|
72
|
+
let panBaseTranslateY = 0
|
|
73
|
+
let pinchStartDistance = 0
|
|
74
|
+
let pinchStartScale = 1
|
|
75
|
+
let pinchStartCenterX = 0
|
|
76
|
+
let pinchStartCenterY = 0
|
|
77
|
+
let pinchLockedTranslateX = 0
|
|
78
|
+
let pinchLockedTranslateY = 0
|
|
79
|
+
let reboundTimer: ReturnType<typeof setTimeout> | null = null
|
|
80
|
+
let momentumTimer: ReturnType<typeof setTimeout> | null = null
|
|
81
|
+
let rowLabelTimer: ReturnType<typeof setTimeout> | null = null
|
|
82
|
+
let lastPanSampleTime = 0
|
|
83
|
+
let lastPanSampleX = 0
|
|
84
|
+
let lastPanSampleY = 0
|
|
85
|
+
let panVelocityX = 0
|
|
86
|
+
let panVelocityY = 0
|
|
87
|
+
let mouseDown = false
|
|
88
|
+
let mouseStartX = 0
|
|
89
|
+
let mouseStartY = 0
|
|
90
|
+
|
|
91
|
+
// ─── Touch State Sync ─────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
const syncActiveTouches = (touches: UniTouch[]) => {
|
|
94
|
+
activeTouches.clear()
|
|
95
|
+
touches.forEach((touch, index) => {
|
|
96
|
+
activeTouches.set(getTouchIdentifier(touch, index), touch)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const patchActiveTouches = (touches: UniTouch[]) => {
|
|
101
|
+
touches.forEach((touch, index) => {
|
|
102
|
+
activeTouches.set(getTouchIdentifier(touch, index), touch)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const removeActiveTouches = (touches: UniTouch[]) => {
|
|
107
|
+
touches.forEach((touch, index) => {
|
|
108
|
+
activeTouches.delete(getTouchIdentifier(touch, index))
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const getEventTouches = (e: UniTouchEvent, phase: 'start' | 'move' | 'end') => {
|
|
113
|
+
const touches = toTouchArray(e.touches)
|
|
114
|
+
const changedTouches = toTouchArray(e.changedTouches)
|
|
115
|
+
|
|
116
|
+
if (phase === 'end') {
|
|
117
|
+
if (touches.length) {
|
|
118
|
+
syncActiveTouches(touches)
|
|
119
|
+
} else {
|
|
120
|
+
removeActiveTouches(changedTouches)
|
|
121
|
+
if (!activeTouches.size) {
|
|
122
|
+
activeTouches.clear()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return Array.from(activeTouches.values())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (touches.length) {
|
|
129
|
+
syncActiveTouches(touches)
|
|
130
|
+
} else {
|
|
131
|
+
patchActiveTouches(changedTouches)
|
|
132
|
+
}
|
|
133
|
+
return Array.from(activeTouches.values())
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const getChangedTouches = (e: UniTouchEvent) => toTouchArray(e.changedTouches)
|
|
137
|
+
|
|
138
|
+
// ─── Overlay State ────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
const clearRowLabelTimer = () => {
|
|
141
|
+
if (rowLabelTimer) {
|
|
142
|
+
clearTimeout(rowLabelTimer)
|
|
143
|
+
rowLabelTimer = null
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const setRowLabelsVisible = (visible: boolean) => {
|
|
148
|
+
clearRowLabelTimer()
|
|
149
|
+
rowLabelsVisible.value = visible
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const showRowLabelOverlay = () => {
|
|
153
|
+
clearRowLabelTimer()
|
|
154
|
+
rowLabelsVisible.value = getShowRowLabels()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Motion State ─────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
const resetPanVelocity = () => {
|
|
160
|
+
panVelocityX = 0
|
|
161
|
+
panVelocityY = 0
|
|
162
|
+
lastPanSampleTime = 0
|
|
163
|
+
lastPanSampleX = 0
|
|
164
|
+
lastPanSampleY = 0
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const recordPanVelocity = (x: number, y: number) => {
|
|
168
|
+
const now = Date.now()
|
|
169
|
+
if (!lastPanSampleTime) {
|
|
170
|
+
lastPanSampleTime = now
|
|
171
|
+
lastPanSampleX = x
|
|
172
|
+
lastPanSampleY = y
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const dt = Math.max(1, now - lastPanSampleTime)
|
|
177
|
+
const nextVelocityX = (x - lastPanSampleX) / dt
|
|
178
|
+
const nextVelocityY = (y - lastPanSampleY) / dt
|
|
179
|
+
|
|
180
|
+
panVelocityX = panVelocityX * 0.35 + nextVelocityX * 0.65
|
|
181
|
+
panVelocityY = panVelocityY * 0.35 + nextVelocityY * 0.65
|
|
182
|
+
lastPanSampleTime = now
|
|
183
|
+
lastPanSampleX = x
|
|
184
|
+
lastPanSampleY = y
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const stopMomentum = () => {
|
|
188
|
+
if (!momentumTimer) return
|
|
189
|
+
clearTimeout(momentumTimer)
|
|
190
|
+
momentumTimer = null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const stopRebound = () => {
|
|
194
|
+
if (!reboundTimer) return
|
|
195
|
+
clearTimeout(reboundTimer)
|
|
196
|
+
reboundTimer = null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const stopMotion = () => {
|
|
200
|
+
stopMomentum()
|
|
201
|
+
stopRebound()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const updateBaseTransform = () => {
|
|
205
|
+
touchHandler.baseScale = touchHandler.scale
|
|
206
|
+
touchHandler.baseTranslateX = touchHandler.translateX
|
|
207
|
+
touchHandler.baseTranslateY = touchHandler.translateY
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const applyTranslate = (x: number, y: number, scale = touchHandler.scale) => {
|
|
211
|
+
const nextTranslate = applyTranslateResistance(x, y, scale)
|
|
212
|
+
touchHandler.translateX = nextTranslate.x
|
|
213
|
+
touchHandler.translateY = nextTranslate.y
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const beginPan = (touch: UniTouch, moved: boolean) => {
|
|
217
|
+
gestureMode = 'pan'
|
|
218
|
+
dragMoved = moved
|
|
219
|
+
panStartX = getTouchX(touch)
|
|
220
|
+
panStartY = getTouchY(touch)
|
|
221
|
+
panBaseTranslateX = touchHandler.translateX
|
|
222
|
+
panBaseTranslateY = touchHandler.translateY
|
|
223
|
+
resetPanVelocity()
|
|
224
|
+
recordPanVelocity(panStartX, panStartY)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const beginPinch = (touches: UniTouch[]) => {
|
|
228
|
+
gestureMode = 'pinch'
|
|
229
|
+
dragMoved = true
|
|
230
|
+
resetPanVelocity()
|
|
231
|
+
pinchStartDistance = getTouchDistance(touches)
|
|
232
|
+
pinchStartScale = touchHandler.scale
|
|
233
|
+
const center = getTouchCenter(touches)
|
|
234
|
+
pinchStartCenterX = center.x
|
|
235
|
+
pinchStartCenterY = center.y
|
|
236
|
+
pinchLockedTranslateX = touchHandler.translateX
|
|
237
|
+
pinchLockedTranslateY = touchHandler.translateY
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Motion Animation ─────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
const reboundToBounds = (onComplete?: () => void) => {
|
|
243
|
+
stopMomentum()
|
|
244
|
+
stopRebound()
|
|
245
|
+
const fromX = touchHandler.translateX
|
|
246
|
+
const fromY = touchHandler.translateY
|
|
247
|
+
const target = clampTranslate(fromX, fromY)
|
|
248
|
+
|
|
249
|
+
if (Math.abs(target.x - fromX) <= reboundThreshold && Math.abs(target.y - fromY) <= reboundThreshold) {
|
|
250
|
+
touchHandler.translateX = target.x
|
|
251
|
+
touchHandler.translateY = target.y
|
|
252
|
+
updateBaseTransform()
|
|
253
|
+
draw()
|
|
254
|
+
emitMove()
|
|
255
|
+
onComplete?.()
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const startTime = Date.now()
|
|
260
|
+
const easeOutCubic = (t: number) => 1 - (1 - t) ** 3
|
|
261
|
+
|
|
262
|
+
const animate = () => {
|
|
263
|
+
const elapsed = Date.now() - startTime
|
|
264
|
+
const progress = Math.min(1, elapsed / reboundDuration)
|
|
265
|
+
const eased = easeOutCubic(progress)
|
|
266
|
+
|
|
267
|
+
touchHandler.translateX = fromX + (target.x - fromX) * eased
|
|
268
|
+
touchHandler.translateY = fromY + (target.y - fromY) * eased
|
|
269
|
+
updateBaseTransform()
|
|
270
|
+
draw()
|
|
271
|
+
emitMove()
|
|
272
|
+
|
|
273
|
+
if (progress >= 1) {
|
|
274
|
+
touchHandler.translateX = target.x
|
|
275
|
+
touchHandler.translateY = target.y
|
|
276
|
+
updateBaseTransform()
|
|
277
|
+
draw()
|
|
278
|
+
emitMove()
|
|
279
|
+
reboundTimer = null
|
|
280
|
+
onComplete?.()
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
reboundTimer = setTimeout(animate, 16)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
animate()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const startMomentum = (onComplete?: () => void) => {
|
|
291
|
+
stopMomentum()
|
|
292
|
+
|
|
293
|
+
if (Math.abs(panVelocityX) < momentumMinVelocity && Math.abs(panVelocityY) < momentumMinVelocity) {
|
|
294
|
+
reboundToBounds(onComplete)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let velocityX = panVelocityX
|
|
299
|
+
let velocityY = panVelocityY
|
|
300
|
+
let lastTime = Date.now()
|
|
301
|
+
|
|
302
|
+
const animate = () => {
|
|
303
|
+
const now = Date.now()
|
|
304
|
+
const dt = Math.min(24, Math.max(8, now - lastTime))
|
|
305
|
+
lastTime = now
|
|
306
|
+
|
|
307
|
+
const decay = Math.pow(momentumDecayPerFrame, dt / 16)
|
|
308
|
+
velocityX *= decay
|
|
309
|
+
velocityY *= decay
|
|
310
|
+
|
|
311
|
+
const nextTranslate = applyTranslateResistance(
|
|
312
|
+
touchHandler.translateX + velocityX * dt,
|
|
313
|
+
touchHandler.translateY + velocityY * dt,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
touchHandler.translateX = nextTranslate.x
|
|
317
|
+
touchHandler.translateY = nextTranslate.y
|
|
318
|
+
updateBaseTransform()
|
|
319
|
+
draw()
|
|
320
|
+
emitMove()
|
|
321
|
+
|
|
322
|
+
const clamped = clampTranslate(touchHandler.translateX, touchHandler.translateY)
|
|
323
|
+
const outOfBounds =
|
|
324
|
+
Math.abs(clamped.x - touchHandler.translateX) > reboundThreshold ||
|
|
325
|
+
Math.abs(clamped.y - touchHandler.translateY) > reboundThreshold
|
|
326
|
+
|
|
327
|
+
if ((Math.abs(velocityX) < momentumMinVelocity && Math.abs(velocityY) < momentumMinVelocity) || outOfBounds) {
|
|
328
|
+
momentumTimer = null
|
|
329
|
+
panVelocityX = velocityX
|
|
330
|
+
panVelocityY = velocityY
|
|
331
|
+
reboundToBounds(onComplete)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
momentumTimer = setTimeout(animate, 16)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
animate()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const emitSeatSelection = (seat: SteSelectSeatItem) => {
|
|
342
|
+
emitSeatClick(seat)
|
|
343
|
+
emitModelValue(toggleSeat(seat.row, seat.col))
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const selectSeatByPoint = (x: number, y: number) => {
|
|
347
|
+
const seat = getTouchSeat(x, y)
|
|
348
|
+
if (seat && !seat.disabled && !seat.empty) {
|
|
349
|
+
emitSeatSelection(seat)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const selectSeatFromTouch = (touch: UniTouch) => {
|
|
354
|
+
uni.createSelectorQuery()
|
|
355
|
+
.in(instance)
|
|
356
|
+
.select(`#${canvasId}`)
|
|
357
|
+
.boundingClientRect((rect: any) => {
|
|
358
|
+
if (!rect) return
|
|
359
|
+
const { x: localX, y: localY } = getTouchLocalPoint(touch, rect)
|
|
360
|
+
selectSeatByPoint(localX, localY)
|
|
361
|
+
})
|
|
362
|
+
.exec()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const selectSeatFromMouse = (event: PointerEventLike) => {
|
|
366
|
+
const rect = (event.target as HTMLElement | null)?.getBoundingClientRect?.()
|
|
367
|
+
if (!rect) return
|
|
368
|
+
selectSeatByPoint(event.clientX - rect.left, event.clientY - rect.top)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ─── Pointer Events ───────────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
const onTouchStart = (e: UniTouchEvent) => {
|
|
374
|
+
stopMotion()
|
|
375
|
+
const touches = getEventTouches(e, 'start')
|
|
376
|
+
const touchCount = touches.length
|
|
377
|
+
|
|
378
|
+
if (touchCount >= 2) {
|
|
379
|
+
beginPinch(touches)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
beginPan(touches[0], false)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const onTouchMove = (e: UniTouchEvent) => {
|
|
387
|
+
const touches = getEventTouches(e, 'move')
|
|
388
|
+
const touchCount = touches.length
|
|
389
|
+
|
|
390
|
+
if (touchCount >= 2) {
|
|
391
|
+
if (!pinchStartDistance) {
|
|
392
|
+
beginPinch(touches)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const currentDistance = getTouchDistance(touches)
|
|
396
|
+
const currentCenter = getTouchCenter(touches)
|
|
397
|
+
if (pinchStartDistance > 0 && currentDistance > 0) {
|
|
398
|
+
const nextScale = clampScale((currentDistance / pinchStartDistance) * pinchStartScale)
|
|
399
|
+
touchHandler.scale = nextScale
|
|
400
|
+
|
|
401
|
+
const nextTranslateX = currentCenter.x / nextScale - pinchStartCenterX / pinchStartScale + pinchLockedTranslateX
|
|
402
|
+
const nextTranslateY = currentCenter.y / nextScale - pinchStartCenterY / pinchStartScale + pinchLockedTranslateY
|
|
403
|
+
applyTranslate(nextTranslateX, nextTranslateY, nextScale)
|
|
404
|
+
}
|
|
405
|
+
draw()
|
|
406
|
+
emitMove()
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (gestureMode !== 'pan' || touchCount !== 1) return
|
|
411
|
+
|
|
412
|
+
const touch = touches[0]
|
|
413
|
+
const currentX = getTouchX(touch)
|
|
414
|
+
const currentY = getTouchY(touch)
|
|
415
|
+
const dx = currentX - panStartX
|
|
416
|
+
const dy = currentY - panStartY
|
|
417
|
+
|
|
418
|
+
if (Math.abs(dx) > panThreshold || Math.abs(dy) > panThreshold) {
|
|
419
|
+
dragMoved = true
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
applyTranslate(panBaseTranslateX + dx, panBaseTranslateY + dy)
|
|
423
|
+
recordPanVelocity(currentX, currentY)
|
|
424
|
+
draw()
|
|
425
|
+
emitMove()
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const onTouchEnd = (e: UniTouchEvent) => {
|
|
429
|
+
const touches = getEventTouches(e, 'end')
|
|
430
|
+
const changedTouches = getChangedTouches(e)
|
|
431
|
+
const touchCount = touches.length
|
|
432
|
+
|
|
433
|
+
if (gestureMode === 'pinch') {
|
|
434
|
+
touchHandler.scale = clampScale(touchHandler.scale)
|
|
435
|
+
applyTranslate(touchHandler.translateX, touchHandler.translateY, touchHandler.scale)
|
|
436
|
+
pinchStartDistance = 0
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
updateBaseTransform()
|
|
440
|
+
|
|
441
|
+
if (touchCount >= 2) {
|
|
442
|
+
beginPinch(touches)
|
|
443
|
+
draw()
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (touchCount === 1) {
|
|
448
|
+
beginPan(touches[0], true)
|
|
449
|
+
} else if (touchCount === 0) {
|
|
450
|
+
gestureMode = 'none'
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
draw()
|
|
454
|
+
if (touchCount === 0 && dragMoved) {
|
|
455
|
+
startMomentum(() => showRowLabelOverlay())
|
|
456
|
+
} else if (touchCount === 0 || gestureMode === 'pinch') {
|
|
457
|
+
reboundToBounds(() => {
|
|
458
|
+
if (touchCount === 0) showRowLabelOverlay()
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!dragMoved && changedTouches.length === 1) {
|
|
463
|
+
selectSeatFromTouch(changedTouches[0])
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const onMouseDown = (e: PointerEventLike) => {
|
|
468
|
+
stopMotion()
|
|
469
|
+
mouseDown = true
|
|
470
|
+
dragMoved = false
|
|
471
|
+
mouseStartX = e.clientX
|
|
472
|
+
mouseStartY = e.clientY
|
|
473
|
+
resetPanVelocity()
|
|
474
|
+
recordPanVelocity(mouseStartX, mouseStartY)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const onMouseMove = (e: PointerEventLike) => {
|
|
478
|
+
if (!mouseDown) return
|
|
479
|
+
const dx = e.clientX - mouseStartX
|
|
480
|
+
const dy = e.clientY - mouseStartY
|
|
481
|
+
if (Math.abs(dx) > 2 || Math.abs(dy) > 2) {
|
|
482
|
+
dragMoved = true
|
|
483
|
+
}
|
|
484
|
+
applyTranslate(touchHandler.baseTranslateX + dx, touchHandler.baseTranslateY + dy)
|
|
485
|
+
recordPanVelocity(e.clientX, e.clientY)
|
|
486
|
+
draw()
|
|
487
|
+
emitMove()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const onMouseUp = (e: PointerEventLike) => {
|
|
491
|
+
if (!mouseDown) return
|
|
492
|
+
mouseDown = false
|
|
493
|
+
updateBaseTransform()
|
|
494
|
+
if (dragMoved) {
|
|
495
|
+
startMomentum(() => showRowLabelOverlay())
|
|
496
|
+
} else {
|
|
497
|
+
reboundToBounds(() => showRowLabelOverlay())
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (!dragMoved) {
|
|
501
|
+
selectSeatFromMouse(e)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ─── Public API ───────────────────────────────────────────────────────────
|
|
506
|
+
|
|
507
|
+
const reset = () => {
|
|
508
|
+
stopMotion()
|
|
509
|
+
showRowLabelOverlay()
|
|
510
|
+
activeTouches.clear()
|
|
511
|
+
touchHandler.reset()
|
|
512
|
+
gestureMode = 'none'
|
|
513
|
+
dragMoved = false
|
|
514
|
+
pinchStartDistance = 0
|
|
515
|
+
pinchStartScale = 1
|
|
516
|
+
pinchStartCenterX = 0
|
|
517
|
+
pinchStartCenterY = 0
|
|
518
|
+
pinchLockedTranslateX = 0
|
|
519
|
+
pinchLockedTranslateY = 0
|
|
520
|
+
panStartX = 0
|
|
521
|
+
panStartY = 0
|
|
522
|
+
panBaseTranslateX = 0
|
|
523
|
+
panBaseTranslateY = 0
|
|
524
|
+
resetPanVelocity()
|
|
525
|
+
applyDefaultViewport()
|
|
526
|
+
draw()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
rowLabelsVisible,
|
|
531
|
+
setShowRowLabelsVisible: setRowLabelsVisible,
|
|
532
|
+
onTouchStart,
|
|
533
|
+
onTouchMove,
|
|
534
|
+
onTouchEnd,
|
|
535
|
+
onMouseDown,
|
|
536
|
+
onMouseMove,
|
|
537
|
+
onMouseUp,
|
|
538
|
+
reset,
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -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
|
+
}
|