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.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/components/TActionSheet/index.uvue +170 -0
- package/components/TActionSheet/type.uts +29 -0
- package/components/TAvatar/index.uvue +156 -0
- package/components/TAvatar/type.uts +54 -0
- package/components/TBadge/index.uvue +152 -0
- package/components/TBadge/type.uts +48 -0
- package/components/TButton/README.md +111 -0
- package/components/TButton/index.uvue +380 -0
- package/components/TButton/type.uts +95 -0
- package/components/TCard/index.uvue +174 -0
- package/components/TCard/type.uts +50 -0
- package/components/TCell/index.uvue +49 -0
- package/components/TCheckbox/index.uvue +187 -0
- package/components/TCheckboxGroup/index.uvue +139 -0
- package/components/TCheckboxGroup/type.uts +26 -0
- package/components/TCol/index.uvue +82 -0
- package/components/TCol/type.uts +30 -0
- package/components/TCollapse/index.uvue +93 -0
- package/components/TCollapse/type.uts +36 -0
- package/components/TCollapseItem/index.uvue +194 -0
- package/components/TCollapseItem/type.uts +25 -0
- package/components/TDialog/index.uvue +386 -0
- package/components/TDialog/type.uts +84 -0
- package/components/TDivider/index.uvue +235 -0
- package/components/TDivider/type.uts +91 -0
- package/components/TEmpty/index.uvue +128 -0
- package/components/TErrorState/index.uvue +57 -0
- package/components/TGrid/index.uvue +115 -0
- package/components/TGrid/type.uts +77 -0
- package/components/TGridItem/index.uvue +243 -0
- package/components/TGridItem/type.uts +64 -0
- package/components/TIcon/index.uvue +96 -0
- package/components/TImage/index.uvue +255 -0
- package/components/TImage/type.uts +146 -0
- package/components/TInput/README.md +119 -0
- package/components/TInput/index.uvue +376 -0
- package/components/TInput/type.uts +138 -0
- package/components/TList/index.uvue +82 -0
- package/components/TList/type.uts +68 -0
- package/components/TListItem/index.uvue +161 -0
- package/components/TListItem/type.uts +49 -0
- package/components/TLoading/index.uvue +153 -0
- package/components/TLoading/type.uts +43 -0
- package/components/TNavBar/index.uvue +120 -0
- package/components/TNavBar/type.uts +22 -0
- package/components/TNoticeBar/index.uvue +106 -0
- package/components/TNoticeBar/type.uts +21 -0
- package/components/TNumberInput/index.uvue +226 -0
- package/components/TPicker/index.uvue +276 -0
- package/components/TPicker/type.uts +105 -0
- package/components/TPopup/index.uvue +442 -0
- package/components/TProgress/index.uvue +103 -0
- package/components/TProgress/type.uts +64 -0
- package/components/TRadioButton/index.uvue +232 -0
- package/components/TRadioGroup/index.uvue +117 -0
- package/components/TRadioGroup/type.uts +25 -0
- package/components/TRate/index.uvue +182 -0
- package/components/TRow/index.uvue +105 -0
- package/components/TRow/type.uts +52 -0
- package/components/TSearchBar/index.uvue +255 -0
- package/components/TSearchBar/type.uts +140 -0
- package/components/TSelect/index.uvue +655 -0
- package/components/TSelect/type.uts +57 -0
- package/components/TSlider/index.uvue +72 -0
- package/components/TSlider/type.uts +21 -0
- package/components/TSwiper/index.uvue +222 -0
- package/components/TSwiper/type.uts +77 -0
- package/components/TSwitch/index.uvue +177 -0
- package/components/TSwitch/type.uts +52 -0
- package/components/TText/README.md +124 -0
- package/components/TText/index.uvue +257 -0
- package/components/TText/type.uts +114 -0
- package/components/TTextarea/index.uvue +239 -0
- package/components/TTextarea/type.uts +106 -0
- package/components/TToast/type.uts +14 -0
- package/components/Tabs/README.md +297 -0
- package/components/Tabs/index.uvue +383 -0
- package/components/Tabs/type.uts +10 -0
- package/components/Tags/README.md +297 -0
- package/components/Tags/index.uvue +383 -0
- package/components/Tags/type.uts +10 -0
- package/components/VbenFrom/index.uvue +392 -0
- package/composables/useModal.uts +294 -0
- package/composables/useTheme.uts +235 -0
- package/composables/useToast.uts +322 -0
- package/index.js +62 -0
- package/package.json +48 -0
- package/style/colors/index.scss +157 -0
- package/style/index.scss +399 -0
- package/types/index.uts +52 -0
- package/uni.scss +79 -0
- package/utils/color.uts +92 -0
- package/utils/common.uts +245 -0
- package/utils/dom.uts +275 -0
- package/utils/index.uts +10 -0
- package/utils/validator.uts +155 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
<script setup lang="uts" >
|
|
2
|
+
|
|
3
|
+
import RadioButton from '@/components/RadioButton/index'
|
|
4
|
+
import CheckboxButton from '@/components/CheckboxButton/index'
|
|
5
|
+
|
|
6
|
+
type FormOption = { label : string; value : string | number }
|
|
7
|
+
type FormSchema = {
|
|
8
|
+
field : string
|
|
9
|
+
label : string
|
|
10
|
+
component : 'input' | 'textarea' | 'select' | 'date' | 'radio' | 'checkbox' | 'switch'
|
|
11
|
+
placeholder ?: string
|
|
12
|
+
required ?: boolean
|
|
13
|
+
options ?: FormOption[]
|
|
14
|
+
type ?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
schemas : FormSchema[]
|
|
19
|
+
activeColor ?: string
|
|
20
|
+
inactiveColor ?: string
|
|
21
|
+
backgroundColor ?: string
|
|
22
|
+
hideButtons ?: boolean // 是否隐藏默认按钮
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
+
activeColor: '#00bba7',
|
|
27
|
+
inactiveColor: '#666666',
|
|
28
|
+
backgroundColor: '#ffffff',
|
|
29
|
+
hideButtons: false,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits(['submit', 'reset'])
|
|
33
|
+
|
|
34
|
+
/** 使用 defineModel 实现双向数据绑定 */
|
|
35
|
+
const model = defineModel<Record<string, any>>({ default: () => ({}) })
|
|
36
|
+
|
|
37
|
+
/** 验证错误状态 */
|
|
38
|
+
const errors = reactive<Record<string, string>>({})
|
|
39
|
+
|
|
40
|
+
/** 动态 CSS 变量 */
|
|
41
|
+
const cssVars = computed(() => ({
|
|
42
|
+
'--hover-active-color': props.activeColor,
|
|
43
|
+
'--hover-inactive-color': props.inactiveColor,
|
|
44
|
+
'--hover-bg-color': props.backgroundColor,
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/** placeholder */
|
|
49
|
+
|
|
50
|
+
const hanldePlaceholder=(item : FormSchema): string => {
|
|
51
|
+
return item.placeholder || `请输入${item.label}`
|
|
52
|
+
}
|
|
53
|
+
/** 处理范围选择器选项 */
|
|
54
|
+
const hanldeRange = (options) => {
|
|
55
|
+
return options?.map(o => o.label) || []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/** select 选中项文字 */
|
|
60
|
+
const getSelectLabel = (item : FormSchema) => {
|
|
61
|
+
const opt = item.options?.find(o => o.value === model.value[item.field])
|
|
62
|
+
return opt?.label || ''
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 选择器变化 */
|
|
66
|
+
const onSelectChange = (e : any, item : FormSchema) => {
|
|
67
|
+
const index = e.detail.value as number
|
|
68
|
+
const value = item.options?.[index]?.value ?? ''
|
|
69
|
+
model.value[item.field] = value
|
|
70
|
+
validateField(item)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/** 选择时间 */
|
|
75
|
+
const onTimeChange = (e : any, item : FormSchema) => {
|
|
76
|
+
model.value[item.field] = e.detail.value
|
|
77
|
+
validateField(item)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 验证单个字段 */
|
|
81
|
+
const validateField = (item : FormSchema) => {
|
|
82
|
+
if (item.required && !model.value[item.field]) {
|
|
83
|
+
errors[item.field] = `请输入${item.label}`
|
|
84
|
+
} else {
|
|
85
|
+
delete errors[item.field]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 单选变化 */
|
|
90
|
+
const onRadioChange = (value : string | number, item : FormSchema) => {
|
|
91
|
+
model.value[item.field] = value
|
|
92
|
+
validateField(item)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 多选变化 */
|
|
96
|
+
const onCheckboxChange = (values : (string | number)[], item : FormSchema) => {
|
|
97
|
+
model.value[item.field] = values
|
|
98
|
+
validateField(item)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 提交表单 */
|
|
102
|
+
const onFormSubmit = async (e: UniFormSubmitEvent): Promise<{ valid: boolean, values: any }> => {
|
|
103
|
+
// 清空所有错误
|
|
104
|
+
Object.keys(errors).forEach(k => delete errors[k])
|
|
105
|
+
|
|
106
|
+
// 验证所有必填项
|
|
107
|
+
let hasError = false
|
|
108
|
+
for (const item of props.schemas) {
|
|
109
|
+
validateField(item)
|
|
110
|
+
if (item.required && !model.value[item.field]) {
|
|
111
|
+
hasError = true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 如果有错误,阻止提交
|
|
116
|
+
if (hasError) {
|
|
117
|
+
console.warn('表单验证失败,存在必填项未填写')
|
|
118
|
+
return { valid: false, values: null }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ✅ 返回异步提交结果
|
|
122
|
+
try {
|
|
123
|
+
await emit('submit', model.value)
|
|
124
|
+
return { valid: true, values: model.value}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error('表单提交失败:', err)
|
|
127
|
+
return { valid: false, values: null }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
/** 重置表单 */
|
|
133
|
+
const onFormReset = () => {
|
|
134
|
+
Object.keys(model.value).forEach(k => (model.value[k] = ''))
|
|
135
|
+
Object.keys(errors).forEach(k => delete errors[k])
|
|
136
|
+
emit('reset')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 对外暴露的方法
|
|
140
|
+
defineExpose({
|
|
141
|
+
/** 提交表单 */
|
|
142
|
+
submit:async () => {
|
|
143
|
+
return await onFormSubmit({} as UniFormSubmitEvent)
|
|
144
|
+
},
|
|
145
|
+
/** 重置表单 */
|
|
146
|
+
reset: () => {
|
|
147
|
+
onFormReset()
|
|
148
|
+
},
|
|
149
|
+
/** 验证所有字段 */
|
|
150
|
+
validate: () => {
|
|
151
|
+
Object.keys(errors).forEach(k => delete errors[k])
|
|
152
|
+
let hasError = false
|
|
153
|
+
for (const item of props.schemas) {
|
|
154
|
+
validateField(item)
|
|
155
|
+
if (item.required && !model.value[item.field]) {
|
|
156
|
+
hasError = true
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return !hasError
|
|
160
|
+
},
|
|
161
|
+
/** 获取表单数据 */
|
|
162
|
+
getFormData: () => ({ ...model.value }),
|
|
163
|
+
/** 获取错误信息 */
|
|
164
|
+
getErrors: () => ({ ...errors })
|
|
165
|
+
})
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<template>
|
|
169
|
+
<view class="vform-container" :style="cssVars">
|
|
170
|
+
<form @submit="onFormSubmit" @reset="onFormReset">
|
|
171
|
+
<view v-for="(item, index) in schemas" class="form-item-box" :key="index">
|
|
172
|
+
<view class="form-item">
|
|
173
|
+
<view class="label">
|
|
174
|
+
<text>{{ item.label }}</text>
|
|
175
|
+
<text v-if="item.required" class="required">*</text>
|
|
176
|
+
</view>
|
|
177
|
+
|
|
178
|
+
<view class="components">
|
|
179
|
+
<input v-if="item.component === 'input'" v-model="model[item.field]" :placeholder="hanldePlaceholder(item)"
|
|
180
|
+
:type="item.type || 'text'" :name="item.field" class="input" @input="validateField(item)" />
|
|
181
|
+
|
|
182
|
+
<!-- 文本域 -->
|
|
183
|
+
<textarea v-if="item.component === 'textarea'" v-model="model[item.field]"
|
|
184
|
+
:placeholder="hanldePlaceholder(item)" :name="item.field" class="textarea"
|
|
185
|
+
@input="validateField(item)"></textarea>
|
|
186
|
+
|
|
187
|
+
<!-- 下拉选择 -->
|
|
188
|
+
<picker v-else-if="item.component === 'select'" mode="selector" :name="item.field"
|
|
189
|
+
:range="hanldeRange(item.options)" @change="onSelectChange($event, item)">
|
|
190
|
+
<view class="select">
|
|
191
|
+
{{ getSelectLabel(item) || '请选择' }}
|
|
192
|
+
</view>
|
|
193
|
+
</picker>
|
|
194
|
+
|
|
195
|
+
<!-- 日期选择 -->
|
|
196
|
+
<picker v-else-if="item.component === 'date'" mode="date" :name="item.field" :value="model[item.field]"
|
|
197
|
+
@change="onTimeChange($event, item)">
|
|
198
|
+
<view class="select">
|
|
199
|
+
{{ model[item.field] || '请选择日期' }}
|
|
200
|
+
</view>
|
|
201
|
+
</picker>
|
|
202
|
+
</view>
|
|
203
|
+
</view>
|
|
204
|
+
<!-- 错误提示 -->
|
|
205
|
+
<view v-if="errors[item.field]" class="error-message">
|
|
206
|
+
<text>{{ errors[item.field] }}</text>
|
|
207
|
+
</view>
|
|
208
|
+
</view>
|
|
209
|
+
|
|
210
|
+
<!-- 默认按钮区域(可通过hideButtons隐藏) -->
|
|
211
|
+
<view v-if="!hideButtons" class="form-footer">
|
|
212
|
+
<button class="btn btn-submit" form-type="submit" type="primary">提交</button>
|
|
213
|
+
<button class="btn btn-reset" form-type="reset" type="default">重置</button>
|
|
214
|
+
</view>
|
|
215
|
+
</form>
|
|
216
|
+
</view>
|
|
217
|
+
</template>
|
|
218
|
+
|
|
219
|
+
<style lang="scss" scoped>
|
|
220
|
+
$color-primary: #14b8a6;
|
|
221
|
+
$color-secondary: #2dd4bf;
|
|
222
|
+
$color-accent: #5eead4;
|
|
223
|
+
$color-error: #ff6b6b;
|
|
224
|
+
$color-warning: #feca57;
|
|
225
|
+
|
|
226
|
+
$color-text: #333;
|
|
227
|
+
$color-border: rgba(0, 0, 0, 0.05);
|
|
228
|
+
$color-bg-light: rgba(255, 255, 255, 0.8);
|
|
229
|
+
$color-disabled: #e0e0e0;
|
|
230
|
+
$color-disabled-text: #999;
|
|
231
|
+
|
|
232
|
+
// 深色模式
|
|
233
|
+
$dark-bg: rgba(30, 30, 30, 0.95);
|
|
234
|
+
$dark-bg-sub: rgba(40, 40, 40, 0.8);
|
|
235
|
+
$dark-bg-item: rgba(50, 50, 50, 0.8);
|
|
236
|
+
$dark-border: rgba(255, 255, 255, 0.1);
|
|
237
|
+
$dark-text: #fff;
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
.vform-container {
|
|
241
|
+
/* 移除默认背景和内边距,完全由父组件控制 */
|
|
242
|
+
background-color: transparent;
|
|
243
|
+
padding: 40rpx 32rpx 32rpx;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.form-item-box {
|
|
247
|
+
/* 移除默认布局,完全由自定义样式控制 */
|
|
248
|
+
align-items: flex-start;
|
|
249
|
+
width: 100%;
|
|
250
|
+
margin: 10rpx 0;
|
|
251
|
+
padding: 16rpx 24rpx;
|
|
252
|
+
border-radius: 16rpx;
|
|
253
|
+
border: 1rpx solid $color-border;
|
|
254
|
+
transition: all 0.3s ease;
|
|
255
|
+
|
|
256
|
+
&:focus-within {
|
|
257
|
+
background: #fff;
|
|
258
|
+
border-color: $color-primary ;
|
|
259
|
+
box-shadow: 0 0 0 4rpx rgba($color-primary, 0.1);
|
|
260
|
+
transform: translateY(-2rpx);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.form-item {
|
|
265
|
+
/* 移除默认布局,完全由自定义样式控制 */
|
|
266
|
+
flex-direction: row;
|
|
267
|
+
align-items: flex-start;
|
|
268
|
+
width: 100%;
|
|
269
|
+
|
|
270
|
+
border-radius: 16rpx;
|
|
271
|
+
background: $color-bg-light;
|
|
272
|
+
transition: all 0.3s ease;
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.label {
|
|
278
|
+
flex-direction: row;
|
|
279
|
+
margin-bottom: 12rpx;
|
|
280
|
+
color: $color-text ;
|
|
281
|
+
font-weight: 500;
|
|
282
|
+
font-size: 28rpx;
|
|
283
|
+
padding: 10rpx 0;
|
|
284
|
+
margin-right: 12rpx;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.required {
|
|
288
|
+
color: $color-error ;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.components {
|
|
292
|
+
flex: 1;
|
|
293
|
+
justify-content: center
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.input,
|
|
297
|
+
.textarea,
|
|
298
|
+
.select {
|
|
299
|
+
/* 移除所有默认样式,完全由自定义样式控制 */
|
|
300
|
+
display: flex;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
border: none;
|
|
303
|
+
padding: 0;
|
|
304
|
+
margin: 0;
|
|
305
|
+
font-size: inherit;
|
|
306
|
+
color: inherit;
|
|
307
|
+
border-radius: 0;
|
|
308
|
+
outline: none;
|
|
309
|
+
resize: none;
|
|
310
|
+
min-height: 34px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* 确保输入框样式完全被自定义样式覆盖 */
|
|
314
|
+
.input:focus,
|
|
315
|
+
.textarea:focus,
|
|
316
|
+
.select:focus {
|
|
317
|
+
width: 100%;
|
|
318
|
+
background: transparent;
|
|
319
|
+
border: none;
|
|
320
|
+
padding: 12rpx 0;
|
|
321
|
+
font-size: 30rpx;
|
|
322
|
+
color: $color-text ;
|
|
323
|
+
transition: all 0.3s ease;
|
|
324
|
+
border-bottom: 2rpx solid transparent;
|
|
325
|
+
text-align: left;
|
|
326
|
+
|
|
327
|
+
&:focus {
|
|
328
|
+
border-bottom-color: $color-primary ;
|
|
329
|
+
outline: none;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.radio-button-container,
|
|
334
|
+
.checkbox-button-container,
|
|
335
|
+
uni-picker {
|
|
336
|
+
width: 100%;
|
|
337
|
+
background: transparent;
|
|
338
|
+
border: none;
|
|
339
|
+
padding: 0;
|
|
340
|
+
text-align: left;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
uni-picker {
|
|
345
|
+
/* 移除默认样式 */
|
|
346
|
+
width: auto;
|
|
347
|
+
text-align: left;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.form-footer {
|
|
351
|
+
flex-direction: row;
|
|
352
|
+
gap: 8px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.btn {
|
|
356
|
+
/* 移除默认按钮样式 */
|
|
357
|
+
flex: 1;
|
|
358
|
+
transition: all 0.3s ease;
|
|
359
|
+
|
|
360
|
+
&:active {
|
|
361
|
+
opacity: 0.7;
|
|
362
|
+
transform: scale(0.98);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.btn-submit {
|
|
367
|
+
background-color: $color-primary;
|
|
368
|
+
color: $dark-text
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.btn-reset {
|
|
372
|
+
border-width: 1px;
|
|
373
|
+
border-style: solid;
|
|
374
|
+
border-color: $color-primary;
|
|
375
|
+
color: $color-primary;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.error-message {
|
|
379
|
+
margin-top: 8rpx;
|
|
380
|
+
text-align: left;
|
|
381
|
+
color: $color-error ;
|
|
382
|
+
font-size: 24rpx;
|
|
383
|
+
line-height: 1.4;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.error-message text {
|
|
387
|
+
color: $color-error;
|
|
388
|
+
font-size: 24rpx;
|
|
389
|
+
line-height: 1.4;
|
|
390
|
+
text-align: right;
|
|
391
|
+
}
|
|
392
|
+
</style>
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模态框管理组合式函数
|
|
3
|
+
* @module composables/useModal
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ref, reactive } from 'vue'
|
|
7
|
+
import type { ModalOptions } from '../types/index.uts'
|
|
8
|
+
|
|
9
|
+
/** 模态框队列 */
|
|
10
|
+
const modalQueue = ref<ModalOptions[]>([])
|
|
11
|
+
|
|
12
|
+
/** 当前显示的模态框 */
|
|
13
|
+
const currentModal = ref<ModalOptions | null>(null)
|
|
14
|
+
|
|
15
|
+
/** 是否显示模态框 */
|
|
16
|
+
const isVisible = ref(false)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 模态框管理组合式函数
|
|
20
|
+
* @returns {Object} 模态框管理对象
|
|
21
|
+
*/
|
|
22
|
+
export function useModal() {
|
|
23
|
+
/**
|
|
24
|
+
* 显示模态框
|
|
25
|
+
* @param {ModalOptions} options - 模态框配置
|
|
26
|
+
* @returns {Promise<boolean>} 用户选择结果
|
|
27
|
+
*/
|
|
28
|
+
const show = (options: ModalOptions): Promise<boolean> => {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
try {
|
|
31
|
+
// 默认配置
|
|
32
|
+
const defaultOptions: ModalOptions = {
|
|
33
|
+
title: '',
|
|
34
|
+
content: '',
|
|
35
|
+
showCancel: true,
|
|
36
|
+
cancelText: '取消',
|
|
37
|
+
confirmText: '确定',
|
|
38
|
+
onConfirm: () => resolve(true),
|
|
39
|
+
onCancel: () => resolve(false)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 合并配置
|
|
43
|
+
const modalConfig = { ...defaultOptions, ...options }
|
|
44
|
+
|
|
45
|
+
// 添加到队列
|
|
46
|
+
modalQueue.value.push(modalConfig)
|
|
47
|
+
|
|
48
|
+
// 如果当前没有显示模态框,则显示第一个
|
|
49
|
+
if (!isVisible.value) {
|
|
50
|
+
showNext()
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('显示模态框失败:', error)
|
|
54
|
+
reject(error)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 显示确认框
|
|
61
|
+
* @param {string} content - 内容
|
|
62
|
+
* @param {string} title - 标题
|
|
63
|
+
* @returns {Promise<boolean>} 用户选择结果
|
|
64
|
+
*/
|
|
65
|
+
const confirm = (content: string, title: string = '提示'): Promise<boolean> => {
|
|
66
|
+
return show({
|
|
67
|
+
title,
|
|
68
|
+
content,
|
|
69
|
+
showCancel: true
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 显示提示框
|
|
75
|
+
* @param {string} content - 内容
|
|
76
|
+
* @param {string} title - 标题
|
|
77
|
+
* @returns {Promise<boolean>} 确认结果
|
|
78
|
+
*/
|
|
79
|
+
const alert = (content: string, title: string = '提示'): Promise<boolean> => {
|
|
80
|
+
return show({
|
|
81
|
+
title,
|
|
82
|
+
content,
|
|
83
|
+
showCancel: false
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 显示输入框
|
|
89
|
+
* @param {string} title - 标题
|
|
90
|
+
* @param {string} placeholder - 占位符
|
|
91
|
+
* @param {string} defaultValue - 默认值
|
|
92
|
+
* @returns {Promise<string | null>} 输入内容
|
|
93
|
+
*/
|
|
94
|
+
const prompt = (title: string, placeholder: string = '', defaultValue: string = ''): Promise<string | null> => {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
// 使用 uni.showModal 的 editable 属性(部分平台支持)
|
|
97
|
+
// #ifdef MP-WEIXIN || MP-ALIPAY
|
|
98
|
+
uni.showModal({
|
|
99
|
+
title,
|
|
100
|
+
content: defaultValue,
|
|
101
|
+
editable: true,
|
|
102
|
+
placeholderText: placeholder,
|
|
103
|
+
success: (res) => {
|
|
104
|
+
if (res.confirm) {
|
|
105
|
+
resolve(res.content || '')
|
|
106
|
+
} else {
|
|
107
|
+
resolve(null)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
fail: () => {
|
|
111
|
+
resolve(null)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
// #endif
|
|
115
|
+
|
|
116
|
+
// 其他平台使用默认模态框
|
|
117
|
+
// #ifndef MP-WEIXIN || MP-ALIPAY
|
|
118
|
+
show({
|
|
119
|
+
title,
|
|
120
|
+
content: `请输入: ${placeholder}`,
|
|
121
|
+
showCancel: true,
|
|
122
|
+
onConfirm: () => resolve(defaultValue),
|
|
123
|
+
onCancel: () => resolve(null)
|
|
124
|
+
})
|
|
125
|
+
// #endif
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 显示加载框
|
|
131
|
+
* @param {string} title - 加载提示
|
|
132
|
+
* @param {boolean} mask - 是否显示遮罩
|
|
133
|
+
* @returns {Function} 关闭函数
|
|
134
|
+
*/
|
|
135
|
+
const loading = (title: string = '加载中...', mask: boolean = true): (() => void) => {
|
|
136
|
+
uni.showLoading({
|
|
137
|
+
title,
|
|
138
|
+
mask
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
uni.hideLoading()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 显示操作菜单
|
|
148
|
+
* @param {string[]} items - 菜单项
|
|
149
|
+
* @param {string} title - 标题
|
|
150
|
+
* @returns {Promise<number>} 选择的索引
|
|
151
|
+
*/
|
|
152
|
+
const actionSheet = (items: string[], title?: string): Promise<number> => {
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
uni.showActionSheet({
|
|
155
|
+
itemList: items,
|
|
156
|
+
title,
|
|
157
|
+
success: (res) => {
|
|
158
|
+
resolve(res.tapIndex)
|
|
159
|
+
},
|
|
160
|
+
fail: () => {
|
|
161
|
+
resolve(-1)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 显示下一个模态框
|
|
169
|
+
*/
|
|
170
|
+
const showNext = () => {
|
|
171
|
+
if (modalQueue.value.length > 0) {
|
|
172
|
+
currentModal.value = modalQueue.value.shift() || null
|
|
173
|
+
isVisible.value = true
|
|
174
|
+
|
|
175
|
+
// 使用 uni.showModal
|
|
176
|
+
if (currentModal.value) {
|
|
177
|
+
uni.showModal({
|
|
178
|
+
title: currentModal.value.title || '',
|
|
179
|
+
content: currentModal.value.content,
|
|
180
|
+
showCancel: currentModal.value.showCancel,
|
|
181
|
+
cancelText: currentModal.value.cancelText,
|
|
182
|
+
confirmText: currentModal.value.confirmText,
|
|
183
|
+
success: (res) => {
|
|
184
|
+
if (res.confirm && currentModal.value?.onConfirm) {
|
|
185
|
+
currentModal.value.onConfirm()
|
|
186
|
+
} else if (res.cancel && currentModal.value?.onCancel) {
|
|
187
|
+
currentModal.value.onCancel()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 处理完当前模态框后,显示下一个
|
|
191
|
+
isVisible.value = false
|
|
192
|
+
currentModal.value = null
|
|
193
|
+
|
|
194
|
+
if (modalQueue.value.length > 0) {
|
|
195
|
+
setTimeout(() => showNext(), 300)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 关闭当前模态框
|
|
205
|
+
*/
|
|
206
|
+
const close = () => {
|
|
207
|
+
if (currentModal.value?.onCancel) {
|
|
208
|
+
currentModal.value.onCancel()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
isVisible.value = false
|
|
212
|
+
currentModal.value = null
|
|
213
|
+
|
|
214
|
+
// 显示下一个
|
|
215
|
+
if (modalQueue.value.length > 0) {
|
|
216
|
+
setTimeout(() => showNext(), 300)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 清空模态框队列
|
|
222
|
+
*/
|
|
223
|
+
const clear = () => {
|
|
224
|
+
modalQueue.value = []
|
|
225
|
+
currentModal.value = null
|
|
226
|
+
isVisible.value = false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 获取队列长度
|
|
231
|
+
* @returns {number} 队列长度
|
|
232
|
+
*/
|
|
233
|
+
const getQueueLength = (): number => {
|
|
234
|
+
return modalQueue.value.length
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
// 状态
|
|
239
|
+
isVisible,
|
|
240
|
+
currentModal,
|
|
241
|
+
modalQueue,
|
|
242
|
+
|
|
243
|
+
// 方法
|
|
244
|
+
show,
|
|
245
|
+
confirm,
|
|
246
|
+
alert,
|
|
247
|
+
prompt,
|
|
248
|
+
loading,
|
|
249
|
+
actionSheet,
|
|
250
|
+
close,
|
|
251
|
+
clear,
|
|
252
|
+
getQueueLength
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 获取模态框实例(单例模式)
|
|
258
|
+
*/
|
|
259
|
+
let modalInstance: ReturnType<typeof useModal> | null = null
|
|
260
|
+
|
|
261
|
+
export function getModalInstance() {
|
|
262
|
+
if (!modalInstance) {
|
|
263
|
+
modalInstance = useModal()
|
|
264
|
+
}
|
|
265
|
+
return modalInstance
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 快捷方法:显示确认框
|
|
270
|
+
*/
|
|
271
|
+
export function showConfirm(content: string, title?: string): Promise<boolean> {
|
|
272
|
+
return getModalInstance().confirm(content, title)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 快捷方法:显示提示框
|
|
277
|
+
*/
|
|
278
|
+
export function showAlert(content: string, title?: string): Promise<boolean> {
|
|
279
|
+
return getModalInstance().alert(content, title)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 快捷方法:显示加载框
|
|
284
|
+
*/
|
|
285
|
+
export function showLoading(title?: string, mask?: boolean): () => void {
|
|
286
|
+
return getModalInstance().loading(title, mask)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 快捷方法:显示操作菜单
|
|
291
|
+
*/
|
|
292
|
+
export function showActionSheet(items: string[], title?: string): Promise<number> {
|
|
293
|
+
return getModalInstance().actionSheet(items, title)
|
|
294
|
+
}
|