tang-ui-x 1.0.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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/components/TActionSheet/index.uvue +170 -0
  4. package/components/TActionSheet/type.uts +29 -0
  5. package/components/TAvatar/index.uvue +156 -0
  6. package/components/TAvatar/type.uts +54 -0
  7. package/components/TBadge/index.uvue +152 -0
  8. package/components/TBadge/type.uts +48 -0
  9. package/components/TButton/README.md +111 -0
  10. package/components/TButton/index.uvue +380 -0
  11. package/components/TButton/type.uts +95 -0
  12. package/components/TCard/index.uvue +174 -0
  13. package/components/TCard/type.uts +50 -0
  14. package/components/TCell/index.uvue +49 -0
  15. package/components/TCheckbox/index.uvue +187 -0
  16. package/components/TCheckboxGroup/index.uvue +139 -0
  17. package/components/TCheckboxGroup/type.uts +26 -0
  18. package/components/TCol/index.uvue +82 -0
  19. package/components/TCol/type.uts +30 -0
  20. package/components/TCollapse/index.uvue +93 -0
  21. package/components/TCollapse/type.uts +36 -0
  22. package/components/TCollapseItem/index.uvue +194 -0
  23. package/components/TCollapseItem/type.uts +25 -0
  24. package/components/TDialog/index.uvue +386 -0
  25. package/components/TDialog/type.uts +84 -0
  26. package/components/TDivider/index.uvue +235 -0
  27. package/components/TDivider/type.uts +91 -0
  28. package/components/TEmpty/index.uvue +128 -0
  29. package/components/TErrorState/index.uvue +57 -0
  30. package/components/TGrid/index.uvue +115 -0
  31. package/components/TGrid/type.uts +77 -0
  32. package/components/TGridItem/index.uvue +243 -0
  33. package/components/TGridItem/type.uts +64 -0
  34. package/components/TIcon/index.uvue +96 -0
  35. package/components/TImage/index.uvue +255 -0
  36. package/components/TImage/type.uts +146 -0
  37. package/components/TInput/README.md +119 -0
  38. package/components/TInput/index.uvue +376 -0
  39. package/components/TInput/type.uts +138 -0
  40. package/components/TList/index.uvue +82 -0
  41. package/components/TList/type.uts +68 -0
  42. package/components/TListItem/index.uvue +161 -0
  43. package/components/TListItem/type.uts +49 -0
  44. package/components/TLoading/index.uvue +153 -0
  45. package/components/TLoading/type.uts +43 -0
  46. package/components/TNavBar/index.uvue +120 -0
  47. package/components/TNavBar/type.uts +22 -0
  48. package/components/TNoticeBar/index.uvue +106 -0
  49. package/components/TNoticeBar/type.uts +21 -0
  50. package/components/TNumberInput/index.uvue +226 -0
  51. package/components/TPicker/index.uvue +276 -0
  52. package/components/TPicker/type.uts +105 -0
  53. package/components/TPopup/index.uvue +442 -0
  54. package/components/TProgress/index.uvue +103 -0
  55. package/components/TProgress/type.uts +64 -0
  56. package/components/TRadioButton/index.uvue +232 -0
  57. package/components/TRadioGroup/index.uvue +117 -0
  58. package/components/TRadioGroup/type.uts +25 -0
  59. package/components/TRate/index.uvue +182 -0
  60. package/components/TRow/index.uvue +105 -0
  61. package/components/TRow/type.uts +52 -0
  62. package/components/TSearchBar/index.uvue +255 -0
  63. package/components/TSearchBar/type.uts +140 -0
  64. package/components/TSelect/index.uvue +655 -0
  65. package/components/TSelect/type.uts +57 -0
  66. package/components/TSlider/index.uvue +72 -0
  67. package/components/TSlider/type.uts +21 -0
  68. package/components/TSwiper/index.uvue +222 -0
  69. package/components/TSwiper/type.uts +77 -0
  70. package/components/TSwitch/index.uvue +177 -0
  71. package/components/TSwitch/type.uts +52 -0
  72. package/components/TText/README.md +124 -0
  73. package/components/TText/index.uvue +257 -0
  74. package/components/TText/type.uts +114 -0
  75. package/components/TTextarea/index.uvue +239 -0
  76. package/components/TTextarea/type.uts +106 -0
  77. package/components/TToast/type.uts +14 -0
  78. package/components/Tabs/README.md +297 -0
  79. package/components/Tabs/index.uvue +383 -0
  80. package/components/Tabs/type.uts +10 -0
  81. package/components/Tags/README.md +297 -0
  82. package/components/Tags/index.uvue +383 -0
  83. package/components/Tags/type.uts +10 -0
  84. package/components/VbenFrom/index.uvue +392 -0
  85. package/composables/useModal.uts +294 -0
  86. package/composables/useTheme.uts +235 -0
  87. package/composables/useToast.uts +322 -0
  88. package/index.js +62 -0
  89. package/package.json +48 -0
  90. package/style/colors/index.scss +157 -0
  91. package/style/index.scss +399 -0
  92. package/types/index.uts +52 -0
  93. package/uni.scss +79 -0
  94. package/utils/color.uts +92 -0
  95. package/utils/common.uts +245 -0
  96. package/utils/dom.uts +275 -0
  97. package/utils/index.uts +10 -0
  98. package/utils/validator.uts +155 -0
@@ -0,0 +1,226 @@
1
+ <script setup lang="uts" >
2
+ type NumberInputProps = {
3
+ /** 最小值 */
4
+ min?: number
5
+ /** 最大值 */
6
+ max?: number
7
+ /** 步长 */
8
+ step?: number
9
+ /** 是否禁用 */
10
+ disabled?: boolean
11
+ /** 占位符 */
12
+ placeholder?: string
13
+ /** 输入框宽度 */
14
+ width?: string | number
15
+ /** 按钮大小 */
16
+ buttonSize?: string | number
17
+ }
18
+
19
+ const model = defineModel({ default: 0 })
20
+
21
+ const props = withDefaults(defineProps<NumberInputProps>(), {
22
+ min: 0,
23
+ max: 999999,
24
+ step: 1,
25
+ disabled: false,
26
+ placeholder: '',
27
+ width: 60,
28
+ buttonSize: 64
29
+ })
30
+
31
+ const emit = defineEmits<{
32
+ change: [value: number]
33
+ }>()
34
+
35
+ /**
36
+ * 处理输入值改变
37
+ */
38
+ const handleInput = (e: any) => {
39
+ const value = parseFloat(e.target.value || '0')
40
+
41
+ // 如果输入无效,使用最小值
42
+ if (isNaN(value)) {
43
+ model.value = props.min
44
+ } else {
45
+ model.value = value
46
+ }
47
+
48
+ // 触发 change 事件
49
+ emit('change', model.value)
50
+ }
51
+
52
+ /**
53
+ * 处理输入框失去焦点
54
+ */
55
+ const handleBlur = () => {
56
+ // 边界检查
57
+ if (model.value < props.min) {
58
+ model.value = props.min
59
+ } else if (model.value > props.max) {
60
+ model.value = props.max
61
+ }
62
+ emit('change', model.value)
63
+ }
64
+
65
+ /**
66
+ * 减少
67
+ */
68
+ const handleDecrease = () => {
69
+ if (props.disabled) return
70
+
71
+ const newValue = model.value - props.step
72
+ if (newValue >= props.min) {
73
+ model.value = newValue
74
+ emit('change', model.value)
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 增加
80
+ */
81
+ const handleIncrease = () => {
82
+ if (props.disabled) return
83
+
84
+ const newValue = model.value + props.step
85
+ if (newValue <= props.max) {
86
+ model.value = newValue
87
+ emit('change', model.value)
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 处理键盘事件
93
+ */
94
+ const handleKeyDown = (e: any) => {
95
+ if (props.disabled) return
96
+
97
+ // 上箭头增加
98
+ if (e.keyCode === 38) {
99
+ e.preventDefault()
100
+ handleIncrease()
101
+ }
102
+ // 下箭头减少
103
+ else if (e.keyCode === 40) {
104
+ e.preventDefault()
105
+ handleDecrease()
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 格式化输入框宽度
111
+ */
112
+ const inputWidth = computed(() => {
113
+ return typeof props.width === 'number' ? props.width + 'rpx' : props.width
114
+ })
115
+
116
+ /**
117
+ * 格式化按钮尺寸
118
+ */
119
+ const buttonSizeComputed = computed(() => {
120
+ return typeof props.buttonSize === 'number' ? props.buttonSize + 'rpx' : props.buttonSize
121
+ })
122
+ </script>
123
+
124
+ <template>
125
+ <view class="number-input-wrapper">
126
+ <!-- 减少按钮 -->
127
+ <view
128
+ class="number-input-button"
129
+ :class="{ 'number-input-button-disabled': disabled || model <= min }"
130
+ :style="{ width: buttonSizeComputed, height: buttonSizeComputed }"
131
+ @tap="handleDecrease">
132
+ <text class="number-input-button-icon">-</text>
133
+ </view>
134
+
135
+ <!-- 输入框 -->
136
+ <input
137
+ class="number-input"
138
+ :class="{ 'number-input-disabled': disabled }"
139
+ :style="{ width: inputWidth }"
140
+ type="number"
141
+ :placeholder="placeholder"
142
+ :value="model"
143
+ :disabled="disabled"
144
+ @input="handleInput"
145
+ @blur="handleBlur"
146
+ @keydown="handleKeyDown" />
147
+
148
+ <!-- 增加按钮 -->
149
+ <view
150
+ class="number-input-button"
151
+ :class="{ 'number-input-button-disabled': disabled || model >= max }"
152
+ :style="{ width: buttonSizeComputed, height: buttonSizeComputed }"
153
+ @tap="handleIncrease">
154
+ <text class="number-input-button-icon">+</text>
155
+ </view>
156
+ </view>
157
+ </template>
158
+
159
+ <style lang="scss" scoped>
160
+ .number-input-wrapper {
161
+ display: flex;
162
+ flex-direction: row;
163
+ align-items: center;
164
+ gap: 8rpx;
165
+ }
166
+
167
+ .number-input-button {
168
+ display: flex;
169
+ align-items: center;
170
+ justify-content: center;
171
+ background-color: #f5f5f5;
172
+ border-radius: 8rpx;
173
+ transition: all 0.3s;
174
+ user-select: none;
175
+ -webkit-user-select: none;
176
+ }
177
+
178
+ .number-input-button:active:not(.number-input-button-disabled) {
179
+ background-color: #e0e0e0;
180
+ transform: scale(0.95);
181
+ }
182
+
183
+ .number-input-button-disabled {
184
+ opacity: 0.4;
185
+ cursor: not-allowed;
186
+ }
187
+
188
+ .number-input-button-icon {
189
+ font-size: 32rpx;
190
+ font-weight: bold;
191
+ color: #333;
192
+ line-height: 1;
193
+ }
194
+
195
+ .number-input {
196
+ height: 64rpx;
197
+ background-color: #f5f5f5;
198
+ border-radius: 8rpx;
199
+ text-align: center;
200
+ font-size: 28rpx;
201
+ color: #333;
202
+ border: 2rpx solid transparent;
203
+ transition: all 0.3s;
204
+ }
205
+
206
+ .number-input:focus {
207
+ border-color: #007aff;
208
+ background-color: #fff;
209
+ outline: none;
210
+ }
211
+
212
+ .number-input-disabled {
213
+ opacity: 0.6;
214
+ cursor: not-allowed;
215
+ }
216
+
217
+ input[type="number"]::-webkit-outer-spin-button,
218
+ input[type="number"]::-webkit-inner-spin-button {
219
+ -webkit-appearance: none;
220
+ margin: 0;
221
+ }
222
+
223
+ input[type="number"] {
224
+ -moz-appearance: textfield;
225
+ }
226
+ </style>
@@ -0,0 +1,276 @@
1
+ <script setup lang="uts">
2
+ import { computed, ref, watch } from 'vue'
3
+ import type { TPickerProps, TPickerOption } from './type.uts'
4
+
5
+ /**
6
+ * TPicker 通用选择器组件
7
+ * @description 支持单列、多列选择
8
+ */
9
+
10
+ // Props 定义 (从 type.uts 导入,排除 visible)
11
+ type Props = Omit<TPickerProps, 'visible'>
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ title: '请选择',
15
+ options: () => [] as TPickerOption[],
16
+ columns: () => [] as TPickerOption[][],
17
+ confirmText: '确定',
18
+ cancelText: '取消',
19
+ itemHeight: '44px',
20
+ visibleItemCount: 5,
21
+ showToolbar: true
22
+ })
23
+
24
+ // 使用 defineModel 管理显示状态 (v-model)
25
+ const visible = defineModel<boolean>({ default: false })
26
+
27
+ // 使用 defineModel 管理选中的值(使用命名模型)
28
+ const selectedValue = defineModel<any>('selectedValue', { default: null })
29
+
30
+ // 定义 emits
31
+ const emit = defineEmits<{
32
+ confirm: [value: any, index: number | number[], option: TPickerOption | TPickerOption[]]
33
+ cancel: []
34
+ change: [value: any, index: number | number[], columnIndex: number]
35
+ }>()
36
+
37
+ // 当前选中的索引(单列为 number,多列为 number[])
38
+ const selectedIndex = ref<number[]>([0])
39
+
40
+ /**
41
+ * 计算是否为单列模式
42
+ */
43
+ const isSingleColumn = computed<boolean>(() => {
44
+ return props.options.length > 0 && props.columns.length === 0
45
+ })
46
+
47
+ /**
48
+ * 计算列数据
49
+ */
50
+ const columnsData = computed<TPickerOption[][]>(() => {
51
+ if (isSingleColumn.value) {
52
+ return [props.options]
53
+ }
54
+ return props.columns
55
+ })
56
+
57
+ /**
58
+ * 计算 picker-view 的高度
59
+ */
60
+ const pickerHeight = computed<string>(() => {
61
+ const height = parseInt(props.itemHeight) * props.visibleItemCount
62
+ return `${height}px`
63
+ })
64
+
65
+ /**
66
+ * 处理遮罩点击
67
+ */
68
+ const handleMaskClick = (): void => {
69
+ handleCancel()
70
+ }
71
+
72
+ /**
73
+ * 处理取消
74
+ */
75
+ const handleCancel = (): void => {
76
+ visible.value = false
77
+ emit('cancel')
78
+ }
79
+
80
+ /**
81
+ * 处理确认
82
+ */
83
+ const handleConfirm = (): void => {
84
+ const columns = columnsData.value
85
+
86
+ if (isSingleColumn.value) {
87
+ // 单列模式
88
+ const index = selectedIndex.value[0] || 0
89
+ const option = columns[0][index]
90
+
91
+ if (option != null) {
92
+ selectedValue.value = option.value
93
+ emit('confirm', option.value, index, option)
94
+ }
95
+ } else {
96
+ // 多列模式
97
+ const values: any[] = []
98
+ const options: TPickerOption[] = []
99
+
100
+ selectedIndex.value.forEach((idx: number, colIdx: number) => {
101
+ const column = columns[colIdx]
102
+ if (column != null && column[idx] != null) {
103
+ values.push(column[idx].value)
104
+ options.push(column[idx])
105
+ }
106
+ })
107
+
108
+ selectedValue.value = values
109
+ emit('confirm', values, selectedIndex.value, options)
110
+ }
111
+
112
+ visible.value = false
113
+ }
114
+
115
+ /**
116
+ * 处理选择器改变
117
+ */
118
+ const handleChange = (e: any): void => {
119
+ const detail = e.detail
120
+ if (detail != null && detail.value != null) {
121
+ selectedIndex.value = detail.value as number[]
122
+
123
+ // 触发 change 事件
124
+ const columns = columnsData.value
125
+ const columnIndex = 0 // 简化处理,实际可能需要判断哪一列改变了
126
+
127
+ if (isSingleColumn.value) {
128
+ const index = selectedIndex.value[0] || 0
129
+ const option = columns[0][index]
130
+ if (option != null) {
131
+ emit('change', option.value, index, columnIndex)
132
+ }
133
+ } else {
134
+ const values: any[] = []
135
+ selectedIndex.value.forEach((idx: number, colIdx: number) => {
136
+ const column = columns[colIdx]
137
+ if (column != null && column[idx] != null) {
138
+ values.push(column[idx].value)
139
+ }
140
+ })
141
+ emit('change', values, selectedIndex.value, columnIndex)
142
+ }
143
+ }
144
+ }
145
+ </script>
146
+
147
+ <template>
148
+ <view v-if="visible" class="t-picker">
149
+ <!-- 遮罩层 -->
150
+ <view class="t-picker__mask" @click="handleMaskClick"></view>
151
+
152
+ <!-- 选择器容器 -->
153
+ <view class="t-picker__container">
154
+ <!-- 工具栏 -->
155
+ <view v-if="showToolbar" class="t-picker__toolbar">
156
+ <view class="t-picker__btn t-picker__btn--cancel" @click="handleCancel">
157
+ <text class="t-picker__btn-text">{{ cancelText }}</text>
158
+ </view>
159
+ <view class="t-picker__title">
160
+ <text class="t-picker__title-text">{{ title }}</text>
161
+ </view>
162
+ <view class="t-picker__btn t-picker__btn--confirm" @click="handleConfirm">
163
+ <text class="t-picker__btn-text t-picker__btn-text--primary">{{ confirmText }}</text>
164
+ </view>
165
+ </view>
166
+
167
+ <!-- 选择器主体 -->
168
+ <picker-view
169
+ class="t-picker__view"
170
+ :style="`height: ${pickerHeight}`"
171
+ :value="selectedIndex"
172
+ @change="handleChange"
173
+ :indicator-style="`height: ${itemHeight}`"
174
+ >
175
+ <picker-view-column v-for="(column, columnIndex) in columnsData" :key="columnIndex">
176
+ <view
177
+ v-for="(item, itemIndex) in column"
178
+ :key="itemIndex"
179
+ class="t-picker__item"
180
+ :class="{ 't-picker__item--disabled': item.disabled }"
181
+ :style="`height: ${itemHeight}`"
182
+ >
183
+ <text class="t-picker__item-text">{{ item.label }}</text>
184
+ </view>
185
+ </picker-view-column>
186
+ </picker-view>
187
+ </view>
188
+ </view>
189
+ </template>
190
+
191
+ <style lang="scss" scoped>
192
+ .t-picker {
193
+ position: fixed;
194
+ top: 0;
195
+ left: 0;
196
+ right: 0;
197
+ bottom: 0;
198
+ z-index: 9999;
199
+
200
+ &__mask {
201
+ position: absolute;
202
+ top: 0;
203
+ left: 0;
204
+ right: 0;
205
+ bottom: 0;
206
+ background-color: rgba(0, 0, 0, 0.5);
207
+ }
208
+
209
+ &__container {
210
+ position: absolute;
211
+ left: 0;
212
+ right: 0;
213
+ bottom: 0;
214
+ background-color: #ffffff;
215
+ border-top-left-radius: 12px;
216
+ border-top-right-radius: 12px;
217
+ z-index: 1;
218
+ }
219
+
220
+ &__toolbar {
221
+ display: flex;
222
+ flex-direction: row;
223
+ align-items: center;
224
+ justify-content: space-between;
225
+ padding: 12px 16px;
226
+ border-bottom: 1px solid #ebeef5;
227
+ }
228
+
229
+ &__btn {
230
+ padding: 8px 12px;
231
+ cursor: pointer;
232
+
233
+ &-text {
234
+ font-size: 16px;
235
+ color: #606266;
236
+
237
+ &--primary {
238
+ color: #409eff;
239
+ font-weight: 500;
240
+ }
241
+ }
242
+ }
243
+
244
+ &__title {
245
+ flex: 1;
246
+ display: flex;
247
+ align-items: center;
248
+ text-align: center;
249
+
250
+ &-text {
251
+ font-size: 16px;
252
+ font-weight: 500;
253
+ color: #303133;
254
+ }
255
+ }
256
+
257
+ &__view {
258
+ width: 100%;
259
+ }
260
+
261
+ &__item {
262
+ display: flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+
266
+ &--disabled {
267
+ opacity: 0.4;
268
+ }
269
+
270
+ &-text {
271
+ font-size: 16px;
272
+ color: #303133;
273
+ }
274
+ }
275
+ }
276
+ </style>
@@ -0,0 +1,105 @@
1
+ /**
2
+ * TPicker 选择器选项类型
3
+ */
4
+ export type TPickerOption = {
5
+ /**
6
+ * 选项文本
7
+ */
8
+ label: string
9
+
10
+ /**
11
+ * 选项值
12
+ */
13
+ value: string | number
14
+
15
+ /**
16
+ * 子选项(用于级联选择)
17
+ */
18
+ children?: TPickerOption[]
19
+
20
+ /**
21
+ * 是否禁用
22
+ */
23
+ disabled?: boolean
24
+ }
25
+
26
+ /**
27
+ * TPicker 选择器组件属性类型定义
28
+ */
29
+ export type TPickerProps = {
30
+ /**
31
+ * 选择器标题
32
+ */
33
+ title?: string
34
+
35
+ /**
36
+ * 选择器数据(单列)
37
+ */
38
+ options?: TPickerOption[]
39
+
40
+ /**
41
+ * 选择器数据(多列)
42
+ */
43
+ columns?: TPickerOption[][]
44
+
45
+ /**
46
+ * 是否显示选择器
47
+ * @default false
48
+ */
49
+ visible?: boolean
50
+
51
+ /**
52
+ * 确认按钮文字
53
+ * @default '确定'
54
+ */
55
+ confirmText?: string
56
+
57
+ /**
58
+ * 取消按钮文字
59
+ * @default '取消'
60
+ */
61
+ cancelText?: string
62
+
63
+ /**
64
+ * 选项高度
65
+ * @default '44px'
66
+ */
67
+ itemHeight?: string
68
+
69
+ /**
70
+ * 可见选项数量
71
+ * @default 5
72
+ */
73
+ visibleItemCount?: number
74
+
75
+ /**
76
+ * 是否显示工具栏
77
+ * @default true
78
+ */
79
+ showToolbar?: boolean
80
+ }
81
+
82
+ /**
83
+ * TPicker 选择器组件事件定义
84
+ */
85
+ export type TPickerEmits = {
86
+ /**
87
+ * 确认选择时触发
88
+ */
89
+ confirm: (value: any, index: number | number[], option: TPickerOption | TPickerOption[]) => void
90
+
91
+ /**
92
+ * 取消选择时触发
93
+ */
94
+ cancel: () => void
95
+
96
+ /**
97
+ * 选项改变时触发
98
+ */
99
+ change: (value: any, index: number | number[], columnIndex: number) => void
100
+
101
+ /**
102
+ * 更新显示状态
103
+ */
104
+ 'update:visible': (value: boolean) => void
105
+ }