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,179 @@
1
+ import { ref, computed, watch } from 'vue'
2
+ import type { SteSelectSeatItem, SteSelectSeatValue } from './types'
3
+ import { getSafeGridSize, isInteger, isSeatInBounds } from './internals/gridUtils'
4
+
5
+ export function useData(props: {
6
+ rows: number
7
+ cols: number
8
+ seats: SteSelectSeatItem[]
9
+ modelValue: SteSelectSeatValue[]
10
+ }) {
11
+ // 内部座位数据 Map: `${row}-${col}` => SteSelectSeatItem
12
+ const seatMap = ref<Map<string, SteSelectSeatItem>>(new Map())
13
+ const warnedMessages = new Set<string>()
14
+
15
+ const getKey = (row: number, col: number) => `${row}-${col}`
16
+
17
+ const warn = (message: string) => {
18
+ if (warnedMessages.has(message)) return
19
+ warnedMessages.add(message)
20
+ console.warn(`[ste-select-seat] ${message}`)
21
+ }
22
+
23
+ const getGridSize = () => {
24
+ const shouldWarn = props.rows !== 0 || props.cols !== 0 || props.seats.length > 0 || props.modelValue.length > 0
25
+ const { rows, cols } = getSafeGridSize(props.rows, props.cols)
26
+
27
+ if (!rows && shouldWarn) {
28
+ warn(`rows 应为大于 0 的整数,当前值为 ${String(props.rows)}`)
29
+ }
30
+ if (!cols && shouldWarn) {
31
+ warn(`cols 应为大于 0 的整数,当前值为 ${String(props.cols)}`)
32
+ }
33
+
34
+ return { rows, cols }
35
+ }
36
+
37
+ const normalizeSeat = (seat: SteSelectSeatItem, index: number, rows: number, cols: number) => {
38
+ if (!isInteger(seat?.row) || !isInteger(seat?.col)) {
39
+ warn(`seats[${index}] 缺少合法的 row/col 整数,已忽略`)
40
+ return null
41
+ }
42
+
43
+ if (!isSeatInBounds(seat.row, seat.col, rows, cols)) {
44
+ warn(`seats[${index}] 的坐标 (${seat.row}, ${seat.col}) 超出范围 rows=${rows}, cols=${cols},已忽略`)
45
+ return null
46
+ }
47
+
48
+ return { ...seat }
49
+ }
50
+
51
+ // 初始化座位数据
52
+ const initSeats = () => {
53
+ const { rows, cols } = getGridSize()
54
+ const map = new Map<string, SteSelectSeatItem>()
55
+
56
+ if (!rows || !cols) {
57
+ seatMap.value = map
58
+ return
59
+ }
60
+
61
+ if (props.seats && props.seats.length > 0) {
62
+ for (let index = 0; index < props.seats.length; index++) {
63
+ const seat = normalizeSeat(props.seats[index], index, rows, cols)
64
+ if (!seat) continue
65
+
66
+ const key = getKey(seat.row, seat.col)
67
+ if (map.has(key)) {
68
+ warn(`seats[${index}] 的坐标 (${seat.row}, ${seat.col}) 重复,后一个配置将覆盖前一个`)
69
+ }
70
+ map.set(key, seat)
71
+ }
72
+ }
73
+
74
+ // 补齐 rows*cols 中未定义的座位
75
+ for (let r = 0; r < rows; r++) {
76
+ for (let c = 0; c < cols; c++) {
77
+ const key = getKey(r, c)
78
+ if (!map.has(key)) {
79
+ map.set(key, { row: r, col: c })
80
+ }
81
+ }
82
+ }
83
+ seatMap.value = map
84
+ }
85
+
86
+ // 获取某个座位数据
87
+ const getSeat = (row: number, col: number): SteSelectSeatItem | undefined => {
88
+ return seatMap.value.get(getKey(row, col))
89
+ }
90
+
91
+ // 设置某个座位数据
92
+ const setSeat = (row: number, col: number, data: Partial<SteSelectSeatItem>) => {
93
+ const { rows, cols } = getGridSize()
94
+ if (!rows || !cols) {
95
+ warn('当前 rows/cols 非法,setSeat 已忽略')
96
+ return
97
+ }
98
+ if (!isInteger(row) || !isInteger(col)) {
99
+ warn(`setSeat 的 row/col 应为整数,当前值为 (${String(row)}, ${String(col)})`)
100
+ return
101
+ }
102
+ if (!isSeatInBounds(row, col, rows, cols)) {
103
+ warn(`setSeat 的坐标 (${row}, ${col}) 超出范围 rows=${rows}, cols=${cols}`)
104
+ return
105
+ }
106
+
107
+ const key = getKey(row, col)
108
+ const existing = seatMap.value.get(key)
109
+ if (existing) {
110
+ seatMap.value.set(key, { ...existing, ...data })
111
+ } else {
112
+ seatMap.value.set(key, { row, col, ...data })
113
+ }
114
+ // 触发响应式更新
115
+ seatMap.value = new Map(seatMap.value)
116
+ }
117
+
118
+ // 获取所有座位
119
+ const getSeats = (): SteSelectSeatItem[] => {
120
+ return Array.from(seatMap.value.values())
121
+ }
122
+
123
+ // 按行获取座位(用于渲染)
124
+ const seatRows = computed(() => {
125
+ const { rows: totalRows, cols: totalCols } = getGridSize()
126
+ const rows: SteSelectSeatItem[][] = []
127
+ for (let r = 0; r < totalRows; r++) {
128
+ const row: SteSelectSeatItem[] = []
129
+ for (let c = 0; c < totalCols; c++) {
130
+ const seat = seatMap.value.get(getKey(r, c))
131
+ if (seat) {
132
+ row.push(seat)
133
+ } else {
134
+ row.push({ row: r, col: c })
135
+ }
136
+ }
137
+ rows.push(row)
138
+ }
139
+ return rows
140
+ })
141
+
142
+ // 判断座位是否选中
143
+ const isSelected = (row: number, col: number): boolean => {
144
+ return props.modelValue.some(v => v.row === row && v.col === col)
145
+ }
146
+
147
+ // 切换座位选中状态,返回新的选中列表
148
+ const toggleSeat = (row: number, col: number): SteSelectSeatValue[] => {
149
+ const seat = getSeat(row, col)
150
+ if (!seat || seat.disabled || seat.empty) return [...props.modelValue]
151
+
152
+ const idx = props.modelValue.findIndex(v => v.row === row && v.col === col)
153
+ const newValue = [...props.modelValue]
154
+ if (idx >= 0) {
155
+ newValue.splice(idx, 1)
156
+ } else {
157
+ newValue.push({ row, col })
158
+ }
159
+ return newValue
160
+ }
161
+
162
+ // 监听 seats 和 rows/cols 变化重新初始化
163
+ watch(
164
+ () => [props.seats, props.rows, props.cols],
165
+ () => initSeats(),
166
+ { immediate: true, deep: true },
167
+ )
168
+
169
+ return {
170
+ seatMap,
171
+ seatRows,
172
+ getSeat,
173
+ setSeat,
174
+ getSeats,
175
+ isSelected,
176
+ toggleSeat,
177
+ initSeats,
178
+ }
179
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * 跨端触摸事件兼容层(纯函数,无外部依赖)
3
+ *
4
+ * APP 端 touch 坐标字段为 x/y,H5 端为 pageX/pageY。
5
+ * APP 端 touches 是类对象 {0: {...}, 1: {...}},没有 length 属性。
6
+ */
7
+ import type { UniTouch, UniTouchEvent } from '../../types/event';
8
+
9
+ /** 获取触摸点 X 坐标(兼容 APP / H5) */
10
+ export const getTouchX = (touch: UniTouch | undefined | null): number => {
11
+ if (!touch) return 0;
12
+ // #ifdef APP
13
+ if (typeof touch.x === 'number') return touch.x;
14
+ // #endif
15
+ if (typeof touch.pageX === 'number') return touch.pageX;
16
+ if (typeof touch.x === 'number') return touch.x;
17
+ return 0;
18
+ };
19
+
20
+ /** 获取触摸点 Y 坐标(兼容 APP / H5) */
21
+ export const getTouchY = (touch: UniTouch | undefined | null): number => {
22
+ if (!touch) return 0;
23
+ // #ifdef APP
24
+ if (typeof touch.y === 'number') return touch.y;
25
+ // #endif
26
+ if (typeof touch.pageY === 'number') return touch.pageY;
27
+ if (typeof touch.y === 'number') return touch.y;
28
+ return 0;
29
+ };
30
+
31
+ /**
32
+ * 将 touches / changedTouches 转为标准数组
33
+ * APP 端返回类对象(无 length),H5/MP 端返回类数组
34
+ */
35
+ export const toTouchArray = (
36
+ touches: UniTouchEvent['touches'] | UniTouchEvent['changedTouches'] | undefined | null
37
+ ): UniTouch[] => {
38
+ if (!touches) return [];
39
+ if (typeof touches.length !== 'number') {
40
+ return Object.keys(touches)
41
+ .filter(k => !isNaN(Number(k)))
42
+ .map(k => (touches as any)[k])
43
+ .filter(Boolean) as UniTouch[];
44
+ }
45
+ return Array.from({ length: touches.length }, (_, index) => touches[index]).filter(Boolean) as UniTouch[];
46
+ };
47
+
48
+ /** 获取触摸点唯一标识(优先 identifier,回退 index) */
49
+ export const getTouchIdentifier = (touch: UniTouch | undefined | null, index: number): number | string => {
50
+ if (touch && typeof touch.identifier === 'number') return touch.identifier;
51
+ return `touch-${index}`;
52
+ };
53
+
54
+ /** 计算两指之间的距离 */
55
+ export const getTouchDistance = (touches: UniTouch[]): number => {
56
+ if (touches.length < 2) return 0;
57
+ const dx = getTouchX(touches[0]) - getTouchX(touches[1]);
58
+ const dy = getTouchY(touches[0]) - getTouchY(touches[1]);
59
+ return Math.sqrt(dx * dx + dy * dy);
60
+ };
61
+
62
+ /** 计算两指中心坐标 */
63
+ export const getTouchCenter = (touches: UniTouch[]): { x: number; y: number } => {
64
+ if (touches.length < 2) {
65
+ return { x: getTouchX(touches[0]), y: getTouchY(touches[0]) };
66
+ }
67
+ return {
68
+ x: (getTouchX(touches[0]) + getTouchX(touches[1])) / 2,
69
+ y: (getTouchY(touches[0]) + getTouchY(touches[1])) / 2,
70
+ };
71
+ };
72
+
73
+ /**
74
+ * 将触摸点转为 canvas 内部本地坐标
75
+ * APP 端 touch.x/y 已是本地坐标;H5 端需减去 bounding rect
76
+ */
77
+ export const getTouchLocalPoint = (
78
+ touch: UniTouch | undefined | null,
79
+ rect?: { left?: number; top?: number } | null
80
+ ): { x: number; y: number } => {
81
+ if (!touch) return { x: 0, y: 0 };
82
+ if (typeof touch.x === 'number' && typeof touch.y === 'number') {
83
+ return { x: touch.x, y: touch.y };
84
+ }
85
+ return {
86
+ x: getTouchX(touch) - (rect?.left ?? 0),
87
+ y: getTouchY(touch) - (rect?.top ?? 0),
88
+ };
89
+ };
@@ -0,0 +1,17 @@
1
+ #### Props
2
+ | 属性名 | 说明 | 类型 | 默认值 | 可选值 | 支持版本 |
3
+ | ----- | ----- | --- | ------- | ------ | -------- |
4
+ | `date` | 展示的日期 | `string / number / Date` | `Date.now()` | - | - |
5
+ | `formatter` | 年月显示的格式化模板 | `string` | `YYYY年M月` | - | - |
6
+ | `weekTexts` | 星期文本数组 | `string[]` | `['周日','周一','周二','周三','周四','周五','周六']` | - | - |
7
+ | `color` | 日期、星期和年月文字颜色 | `string` | `#000000` | - | - |
8
+ | `showCalendar` | 点击后是否弹出内置日历 | `boolean` | `true` | - | - |
9
+ | `customStyle` | 自定义根节点样式对象 | `object` | `{}` | - | - |
10
+ | `customClass` | 自定义根节点 class | `string` | `` | - | - |
11
+
12
+
13
+ #### Events
14
+ | 事件名 | 说明 | 事件参数 | 支持版本 |
15
+ | ----- | ----- | ------- | -------- |
16
+ | `click` | 点击组件时触发 | - | - |
17
+ | `date-change` | 当 date 属性变化时触发 | - | - |
@@ -0,0 +1,112 @@
1
+ # SimpleCalendar 简单日历
2
+
3
+ 轻量级日期展示组件,由日期、星期和年月三部分组成。点击组件后可弹出日历进行日期选择。
4
+
5
+ ---$
6
+
7
+ ### 代码演示
8
+
9
+ #### 基础使用
10
+
11
+ 默认展示当前日期,点击组件后会弹出日历进行选择。
12
+
13
+ ```html
14
+ <template>
15
+ <ste-simple-calendar />
16
+ </template>
17
+ ```
18
+
19
+ #### 指定日期
20
+
21
+ 通过`date`属性设置展示日期,支持时间戳、日期字符串和`Date`对象。
22
+
23
+ ```html
24
+ <template>
25
+ <ste-simple-calendar date="2026-02-24" />
26
+ </template>
27
+ ```
28
+
29
+ #### 禁用日历弹窗
30
+
31
+ - 通过`showCalendar`属性可以关闭内置日历弹窗
32
+ - 关闭后可结合`click`事件自行处理点击逻辑
33
+
34
+ ```html
35
+ <script setup lang="ts">
36
+ const handleClick = (dateInfo: any) => {
37
+ console.log('点击日期:', dateInfo);
38
+ };
39
+ </script>
40
+
41
+ <template>
42
+ <ste-simple-calendar :showCalendar="false" @click="handleClick" />
43
+ </template>
44
+ ```
45
+
46
+ #### 自定义年月格式
47
+
48
+ 通过`formatter`属性设置年月展示格式,格式规则与`dayjs`一致。
49
+
50
+ ```html
51
+ <template>
52
+ <ste-simple-calendar formatter="YYYY/MM" />
53
+ </template>
54
+ ```
55
+
56
+ #### 自定义星期文案
57
+
58
+ 通过`weekTexts`属性设置星期显示内容。
59
+
60
+ ```html
61
+ <template>
62
+ <ste-simple-calendar :weekTexts="['日', '一', '二', '三', '四', '五', '六']" />
63
+ </template>
64
+ ```
65
+
66
+ #### 自定义颜色
67
+
68
+ 通过`color`属性统一设置日期、星期和年月文字颜色。
69
+
70
+ ```html
71
+ <template>
72
+ <ste-simple-calendar color="#0090FF" />
73
+ </template>
74
+ ```
75
+
76
+ #### 调用实例方法
77
+
78
+ 通过`ref`可以调用组件暴露的方法,例如重置为今天。
79
+
80
+ ```html
81
+ <script setup lang="ts">
82
+ import { ref } from 'vue';
83
+
84
+ const calendarRef = ref();
85
+
86
+ const setToday = () => {
87
+ calendarRef.value?.today();
88
+ };
89
+ </script>
90
+
91
+ <template>
92
+ <ste-button @click="setToday">回到今天</ste-button>
93
+ <ste-simple-calendar ref="calendarRef" date="2026-02-24" />
94
+ </template>
95
+ ```
96
+
97
+ ---$
98
+
99
+ ### API
100
+
101
+ <!-- props -->
102
+
103
+ #### Methods
104
+
105
+ | 方法名 | 说明 | 方法参数 | 支持版本 |
106
+ | ------------- | ---------------- | -------------------------- | -------- |
107
+ | `setDate` | 设置当前展示日期 | `string \| number \| Date` | - |
108
+ | `getDateInfo` | 获取当前日期信息 | - | - |
109
+ | `today` | 重置为今天 | - | - |
110
+
111
+ ---$
112
+ {{fuyuwei}}
@@ -0,0 +1,5 @@
1
+ {
2
+ "group": "业务组件",
3
+ "title": "SimpleCalendar 简单日历",
4
+ "icon": "https://image.whzb.com/chain/StellarUI/组件图标/simple-calendar.png"
5
+ }
@@ -0,0 +1,32 @@
1
+ import type { PropType, CSSProperties } from 'vue';
2
+
3
+ export default {
4
+ date: {
5
+ type: [String, Number, Date] as PropType<string | number | Date>,
6
+ default: () => Date.now(),
7
+ },
8
+ formatter: {
9
+ type: String,
10
+ default: () => 'YYYY年M月',
11
+ },
12
+ weekTexts: {
13
+ type: Array as PropType<string[]>,
14
+ default: () => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
15
+ },
16
+ color: {
17
+ type: String,
18
+ default: () => '#000000',
19
+ },
20
+ showCalendar: {
21
+ type: Boolean,
22
+ default: () => true,
23
+ },
24
+ customStyle: {
25
+ type: Object as PropType<CSSProperties>,
26
+ default: () => ({}),
27
+ },
28
+ customClass: {
29
+ type: String,
30
+ default: () => '',
31
+ },
32
+ };
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "ste-simple-calendar",
3
+ "description": "简单日历组件,紧凑展示当前日期信息",
4
+ "example": "<ste-simple-calendar></ste-simple-calendar>",
5
+ "tutorial": "",
6
+ "attributes": [
7
+ {
8
+ "name": "date",
9
+ "description": "展示的日期",
10
+ "type": "string | number | Date",
11
+ "default": "Date.now()"
12
+ },
13
+ {
14
+ "name": "formatter",
15
+ "description": "年月显示的格式化模板",
16
+ "type": "string",
17
+ "default": "YYYY年M月"
18
+ },
19
+ {
20
+ "name": "weekTexts",
21
+ "description": "星期文本数组",
22
+ "type": "string[]",
23
+ "default": "['周日','周一','周二','周三','周四','周五','周六']"
24
+ },
25
+ {
26
+ "name": "color",
27
+ "description": "日期、星期和年月文字颜色",
28
+ "type": "string",
29
+ "default": "#000000"
30
+ },
31
+ {
32
+ "name": "showCalendar",
33
+ "description": "点击后是否弹出内置日历",
34
+ "type": "boolean",
35
+ "default": true
36
+ },
37
+ {
38
+ "name": "customStyle",
39
+ "description": "自定义根节点样式对象",
40
+ "type": "object",
41
+ "default": "{}"
42
+ },
43
+ {
44
+ "name": "customClass",
45
+ "description": "自定义根节点 class",
46
+ "type": "string",
47
+ "default": ""
48
+ },
49
+ {
50
+ "name": "[event]click",
51
+ "description": "点击组件时触发",
52
+ "type": "(dateInfo: DateInfo) => void"
53
+ },
54
+ {
55
+ "name": "[event]date-change",
56
+ "description": "当 date 属性变化时触发",
57
+ "type": "(dateInfo: DateInfo) => void"
58
+ }
59
+ ]
60
+ }