v-uni-app-ui 1.0.4 → 1.0.7

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 (89) hide show
  1. package/README.md +62 -82
  2. package/dist/v-uni-app-ui.css +1 -0
  3. package/dist/v-uni-app-ui.es.js +6569 -0
  4. package/dist/v-uni-app-ui.umd.js +7 -0
  5. package/package.json +17 -5
  6. package/components/config/css/basic.scss +0 -19
  7. package/components/config/interface/basic-type.js +0 -16
  8. package/components/config/interface/components-interface.ts +0 -0
  9. package/components/config/interface/monitor/components/input-monitor.js +0 -0
  10. package/components/config/interface/monitor/property-monitor.ts +0 -136
  11. package/components/config/interface/props/basic-props.ts +0 -88
  12. package/components/config/interface/props/components/button-props.ts +0 -85
  13. package/components/config/interface/props/components/input-props.ts +0 -69
  14. package/components/config/interface/props/props-tools.ts +0 -64
  15. package/components/config/style/basic.js +0 -346
  16. package/components/config/style/component-registry.js +0 -142
  17. package/components/config/style/components/button-style.js +0 -160
  18. package/components/config/style/components/input-style.js +0 -98
  19. package/components/config/style/components-style.js +0 -622
  20. package/components/config/style/property-mapper.js +0 -377
  21. package/components/config/style/pseudo-processor.js +0 -213
  22. package/components/config.js +0 -123
  23. package/components/icon/iconfont.css +0 -87
  24. package/components/icon/iconfont.js +0 -1
  25. package/components/icon/iconfont.json +0 -135
  26. package/components/icon/iconfont.ttf +0 -0
  27. package/components/icon/iconfont.woff +0 -0
  28. package/components/icon/iconfont.woff2 +0 -0
  29. package/components/layout/v-card/v-card.vue +0 -108
  30. package/components/layout/v-grid/v-grid.vue +0 -162
  31. package/components/layout/v-icon-grid/v-icon-grid.vue +0 -195
  32. package/components/layout/v-infinite-scroll/v-infinite-scroll.vue +0 -172
  33. package/components/layout/v-list/v-list.vue +0 -43
  34. package/components/layout/v-row/v-row.vue +0 -142
  35. package/components/layout/v-waterfall/v-waterfall.vue +0 -79
  36. package/components/model/compound/v-checkbox-group/v-checkbox-group.vue +0 -96
  37. package/components/model/compound/v-console/v-console.js +0 -20
  38. package/components/model/compound/v-console/v-console.vue +0 -299
  39. package/components/model/compound/v-date-time/v-date-time.vue +0 -261
  40. package/components/model/compound/v-dialog/v-dialog.vue +0 -178
  41. package/components/model/compound/v-drum-select-picker/v-drum-select-picker.vue +0 -83
  42. package/components/model/compound/v-form/v-form.vue +0 -226
  43. package/components/model/compound/v-form-item/v-form-item.vue +0 -255
  44. package/components/model/compound/v-image/v-image.vue +0 -357
  45. package/components/model/compound/v-input-desensitize/v-input-desensitize.vue +0 -101
  46. package/components/model/compound/v-page/v-page.vue +0 -11
  47. package/components/model/compound/v-pages/v-pages.vue +0 -141
  48. package/components/model/compound/v-picker-list/v-picker-list.vue +0 -109
  49. package/components/model/compound/v-popup/v-popup.vue +0 -151
  50. package/components/model/compound/v-radio-group/v-radio-group.vue +0 -86
  51. package/components/model/compound/v-select-picker/v-select-picker.vue +0 -202
  52. package/components/model/compound/v-series-picker-list/v-series-picker-list.vue +0 -221
  53. package/components/model/compound/v-series-select-picker/v-series-select-picker.vue +0 -203
  54. package/components/model/compound/v-switch/v-switch.vue +0 -136
  55. package/components/model/compound/v-tabs-page/v-tabs-page.vue +0 -138
  56. package/components/model/native/v-badge/v-badge.vue +0 -143
  57. package/components/model/native/v-button/v-button.vue +0 -81
  58. package/components/model/native/v-carousel/v-carousel.vue +0 -138
  59. package/components/model/native/v-checkbox/v-checkbox.vue +0 -215
  60. package/components/model/native/v-collapse/v-collapse.vue +0 -190
  61. package/components/model/native/v-header-navigation-bar/v-header-navigation-bar.vue +0 -92
  62. package/components/model/native/v-input/v-input.vue +0 -163
  63. package/components/model/native/v-input-code/v-input-code.vue +0 -146
  64. package/components/model/native/v-loading/v-loading.vue +0 -206
  65. package/components/model/native/v-menu/v-menu.vue +0 -222
  66. package/components/model/native/v-menu-slide/v-menu-slide.vue +0 -364
  67. package/components/model/native/v-min-loading/v-min-loading.vue +0 -80
  68. package/components/model/native/v-null/v-null.vue +0 -97
  69. package/components/model/native/v-overlay/v-overlay.vue +0 -96
  70. package/components/model/native/v-pull-up-refresh/v-pull-up-refresh.vue +0 -157
  71. package/components/model/native/v-radio/v-radio.vue +0 -138
  72. package/components/model/native/v-scroll-list/v-scroll-list.vue +0 -169
  73. package/components/model/native/v-steps/v-steps.vue +0 -253
  74. package/components/model/native/v-table/v-table.vue +0 -203
  75. package/components/model/native/v-tabs/v-tabs.vue +0 -235
  76. package/components/model/native/v-tag/v-tag.vue +0 -206
  77. package/components/model/native/v-text/v-text.vue +0 -187
  78. package/components/model/native/v-textarea/v-textarea.vue +0 -178
  79. package/components/model/native/v-title/v-title.vue +0 -91
  80. package/components/model/native/v-toast/info.png +0 -0
  81. package/components/model/native/v-toast/success.png +0 -0
  82. package/components/model/native/v-toast/v-toast.vue +0 -198
  83. package/components/model/native/v-toast/warn.png +0 -0
  84. package/components/model/native/v-upload-file-button/v-upload-file-button.vue +0 -296
  85. package/components/model/native/v-video/v-video.vue +0 -175
  86. package/components/model/native/v-window/v-window.vue +0 -158
  87. package/components/utils/event-modifiers.ts +0 -139
  88. package/components/utils/validator.ts +0 -451
  89. package/index.js +0 -372
@@ -1,255 +0,0 @@
1
- <template>
2
- <view :class="['form-item', `form-item--border--${borderModel}`, { 'form-item--hint--model': !hintModel }]">
3
- <view class="title">
4
- <view class="required" v-if="requiredPosition == 'left'">
5
- <v-text v-if="isRequired">*</v-text>
6
- </view>
7
- <view v-if="label" class="label">
8
- {{ label }}
9
- </view>
10
- <view class="required" v-if="requiredPosition == 'right'">
11
- <v-text v-if="isRequired">*</v-text>
12
- </view>
13
- </view>
14
- <view class="control">
15
- <slot></slot>
16
- <view class="error-message" v-if="hintModel">
17
- <v-text v-if="error" type="danger">{{ error }}</v-text>
18
- </view>
19
- </view>
20
- <v-toast ref="toast"></v-toast>
21
- </view>
22
- </template>
23
-
24
- <script lang="ts" setup>
25
- import { ref, inject, onMounted, onUnmounted } from 'vue';
26
- import { getCurrentInstance } from 'vue';
27
-
28
- const { proxy } = getCurrentInstance();
29
-
30
- const props = defineProps({
31
- label: {
32
- type: String,
33
- default: ''
34
- },
35
- name: {
36
- type: String,
37
- required: true
38
- },
39
- requiredPosition: {
40
- type: String,
41
- default: 'right'
42
- },
43
- hintModel: {
44
- type: Boolean,
45
- default: true
46
- },
47
- borderModel: {
48
- type: String,
49
- default: 'bottom',
50
- validator: (v) => ['all', 'none', 'bottom', 'top', 'left', 'right', 'ends', 'up-down'].includes(v)
51
- }
52
- });
53
-
54
- const toast = ref(null);
55
- const error = ref('');
56
- const isRequired = ref(false);
57
- const config = inject('config');
58
- const formContext = inject('form', {
59
- rules: {},
60
- formData: {},
61
- register: (name, validate) => {
62
- console.log(name, validate);
63
- },
64
- unregister: (name) => {
65
- console.log(name);
66
- },
67
- formHasError:{
68
- value:false
69
- },
70
- });
71
-
72
- const validate = async () => {
73
- const rules = formContext.rules[props.name] || [];
74
- const value = formContext.formData[props.name];
75
- const formData = formContext.formData;
76
-
77
- for (const rule of rules) {
78
- // 必填校验
79
- if (rule.required) {
80
- let isEmpty = false;
81
- if (value === null || value === undefined || value === '') {
82
- isEmpty = true;
83
- } else if (typeof value === 'string' || typeof value === 'number') {
84
- if (value != null && value !== undefined) {
85
- isEmpty = !value.toString().trim();
86
- }
87
- } else if (Array.isArray(value)) {
88
- isEmpty = value.length === 0;
89
- } else if (typeof value === 'object') {
90
- isEmpty = Object.keys(value).length === 0;
91
- }
92
- if (isEmpty) {
93
- hintModelMessage(rule.message || `${props.label}不能为空`);
94
- return false;
95
- }
96
- }
97
-
98
- // 类型转换校验(非空后执行)
99
- let processedValue = value;
100
- if ((rule.type === 'number' || rule.type === 'integer') && value !== null && value !== undefined) {
101
- processedValue = Number(value);
102
- if (isNaN(processedValue)) {
103
- hintModelMessage(rule.message || `${props.label}必须为数字`);
104
- return false;
105
- }
106
- if (rule.type === 'integer' && !Number.isInteger(processedValue)) {
107
- hintModelMessage(rule.message || `${props.label}必须为整数`);
108
- return false;
109
- }
110
- }
111
-
112
- // 格式校验
113
- if (value !== null && value !== undefined && value !== '') {
114
- if (rule.pattern && !rule.pattern.test(value)) {
115
- hintModelMessage(rule.message || `${props.label}格式错误`);
116
- return false;
117
- }
118
-
119
- // 内置类型校验
120
- if (rule.type === 'email' && !/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value)) {
121
- hintModelMessage(rule.message || `请输入有效的邮箱地址`);
122
- return false;
123
- }
124
-
125
- if (rule.type === 'mobile' && !/^1[3-9]\d{9}$/.test(value)) {
126
- hintModelMessage(rule.message || `请输入有效的手机号码`);
127
- return false;
128
- }
129
-
130
- // 长度校验
131
- if (rule.minLength !== undefined && value.length < rule.minLength) {
132
- hintModelMessage(rule.message || `${props.label}至少需要${rule.minLength}个字符`);
133
- return false;
134
- }
135
-
136
- if (rule.maxLength !== undefined && value.length > rule.maxLength) {
137
- hintModelMessage(rule.message || `${props.label}不能超过${rule.maxLength}个字符`);
138
- return false;
139
- }
140
-
141
- // 数值范围校验
142
- if (typeof rule.min === 'number' && processedValue < rule.min) {
143
- hintModelMessage(rule.message || `${props.label}不能小于${rule.min}`);
144
- return false;
145
- }
146
-
147
- if (typeof rule.max === 'number' && processedValue > rule.max) {
148
- hintModelMessage(rule.message || `${props.label}不能大于${rule.max}`);
149
- return false;
150
- }
151
-
152
- // 关联字段校验(如密码确认)
153
- if (rule.compareWith) {
154
- const compareValue = formData[rule.compareWith];
155
- if (value !== compareValue) {
156
- hintModelMessage(rule.message || `${props.label}必须与${rule.compareWith}保持一致`);
157
- return false;
158
- }
159
- }
160
-
161
- // 自定义校验函数
162
- if (rule.validator) {
163
- const result = await rule.validator(value, formData);
164
- if (!result) {
165
- hintModelMessage(rule.message || `${props.label}验证未通过`);
166
- return false;
167
- }
168
- }
169
- }
170
- }
171
-
172
- error.value = '';
173
- return true;
174
- };
175
-
176
- const hintModelMessage = (message: string) => {
177
- if (props.hintModel) {
178
- error.value = message;
179
- } else {
180
- if (formContext && formContext.formHasError.value) {
181
- return;
182
- }
183
- if (toast.value) {
184
- toast.value.show({
185
- message: message,
186
- type: 'error',
187
- duration: 3000
188
- });
189
- formContext.formHasError.value = true;
190
- }
191
- }
192
- };
193
-
194
- const resetField = () => {
195
- error.value = '';
196
- formContext.formData[props.name] = null;
197
- };
198
-
199
- // 注册到父表单
200
- onMounted(() => {
201
- if (formContext && formContext.register) {
202
- formContext.register(props.name, { validate, resetField });
203
- if (formContext.rules && formContext.rules[props.name]) {
204
- isRequired.value = formContext.rules[props.name].some((r) => r.required);
205
- }
206
- }
207
- });
208
-
209
- onUnmounted(() => {
210
- if (formContext && formContext.unregister) {
211
- formContext.unregister(props.name);
212
- }
213
- });
214
- </script>
215
- <style lang="scss" scoped>
216
- .form-item {
217
- &--hint--model {
218
- padding-bottom: 20rpx;
219
- }
220
-
221
- &--border--all {
222
- border: 1rpx solid v-bind('config.VFormItem.borderColor');
223
- }
224
-
225
- &--border--none {
226
- border: none;
227
- }
228
-
229
- &--border--bottom {
230
- border-bottom: 1rpx solid v-bind('config.VFormItem.borderColor');
231
- }
232
-
233
- &--border--top {
234
- border-top: 1rpx solid v-bind('config.VFormItem.borderColor');
235
- }
236
-
237
- &--border--left {
238
- border-left: 1rpx solid v-bind('config.VFormItem.borderColor');
239
- }
240
-
241
- &--border--right {
242
- border-right: 1rpx solid v-bind('config.VFormItem.borderColor');
243
- }
244
-
245
- &--border--ends {
246
- border-left: 1rpx solid v-bind('config.VFormItem.borderColor');
247
- border-right: 1rpx solid v-bind('config.VFormItem.borderColor');
248
- }
249
-
250
- &--border--up-down {
251
- border-top: 1rpx solid v-bind('config.VFormItem.borderColor');
252
- border-bottom: 1rpx solid v-bind('config.VFormItem.borderColor');
253
- }
254
- }
255
- </style>
@@ -1,357 +0,0 @@
1
- <template>
2
- <view class="v-image-container" @click="handleClick">
3
- <!-- 根据环境选择不同的标签 -->
4
- <template v-if="!isError">
5
- <image
6
- v-if="isAppPlus"
7
- ref="imgElement"
8
- :src="finalSrc"
9
- :class="['v-image', `v-image--${model}`, { 'v-image--loading': isLoading, 'v-image--disabled': disabled }]"
10
- @click="handlePreview"
11
- @load="handleLoad"
12
- @error="handleError"
13
- />
14
- <img
15
- v-else
16
- ref="imgElement"
17
- :src="finalSrc"
18
- :class="['v-image', `v-image--${model}`, { 'v-image--loading': isLoading, 'v-image--disabled': disabled }]"
19
- @click="handlePreview"
20
- @load="handleLoad"
21
- @error="handleError"
22
- />
23
- </template>
24
-
25
- <slot name="loading" v-if="isLoading">
26
- <view class="v-image-loading">
27
- <v-loading text="加载中……" show />
28
- </view>
29
- </slot>
30
- <slot name="error" v-if="isError">
31
- <view class="v-image-error">
32
- <text class="error-text">加载失败</text>
33
- </view>
34
- </slot>
35
- </view>
36
- <v-overlay v-model:value="showPreview" @click="closePreview" v-if="showPreview">
37
- <image v-if="isAppPlus" :src="finalPreviewSrc" :class="['v-image-preview__img', `v-image-preview__img--${previewModel}`]" @click.stop />
38
- <img v-else :src="finalPreviewSrc" :class="['v-image-preview__img', `v-image-preview__img--${previewModel}`]" @click.stop />
39
- </v-overlay>
40
- </template>
41
-
42
- <script lang="ts" setup>
43
- import { ref, watch, onMounted, onUnmounted, inject, computed } from 'vue';
44
- const props = defineProps({
45
- src: {
46
- type: String,
47
- default: '',
48
- required: true
49
- },
50
- width: {
51
- type: String,
52
- default: '100%'
53
- },
54
- height: {
55
- type: String,
56
- default: '100%'
57
- },
58
- model: {
59
- type: String,
60
- default: 'square',
61
- validator: (val: string) => ['circle', 'rounded', 'square'].includes(val)
62
- },
63
- borderRadius: {
64
- type: String,
65
- default: '10rpx'
66
- },
67
- previewWidth: {
68
- type: String,
69
- default: 'auto'
70
- },
71
- previewHeight: {
72
- type: String,
73
- default: 'auto'
74
- },
75
- enablePreview: {
76
- type: Boolean,
77
- default: false
78
- },
79
- previewModel: {
80
- type: String,
81
- default: 'square',
82
- validator: (val: string) => ['circle', 'rounded', 'square'].includes(val)
83
- },
84
- previewBorderRadius: {
85
- type: String,
86
- default: '10rpx'
87
- },
88
- loading: {
89
- type: Boolean,
90
- default: false
91
- },
92
- disabled: {
93
- type: Boolean,
94
- default: false
95
- },
96
- lazy: {
97
- type: Boolean,
98
- default: false
99
- },
100
- threshold: {
101
- type: Number,
102
- default: 0.1
103
- },
104
- delay: {
105
- type: Number,
106
- default: 800
107
- },
108
- isLoading: {
109
- type: Boolean,
110
- default: false
111
- },
112
- retryCount: {
113
- type: Number,
114
- default: 3
115
- },
116
- fit: {
117
- type: String,
118
- default: 'cover'
119
- },
120
- previewFit: {
121
- type: String,
122
- default: ''
123
- }
124
- });
125
-
126
- const emit = defineEmits(['update:enablePreview', 'update:loading', 'handleLoading', 'handError', 'click']);
127
-
128
- const config = inject<any>('config');
129
- const showPreview = ref(false);
130
- const isLoading = ref(props.isLoading);
131
- const isError = ref(false);
132
- const imgElement = ref<HTMLElement | null>(null);
133
- const displaySrc = ref<string | null>(null);
134
-
135
- let lazyLoadTimer: any = null;
136
- let scrollHandler: any = null;
137
- const retryCount = ref(0);
138
- const maxRetryCount = ref(props.retryCount);
139
-
140
- // 判断是否是 App 环境
141
- const isAppPlus = ref(false);
142
-
143
- // 判断是否是 HTTP 或 HTTPS 链接
144
- const isHttpOrHttps = (url: string) => {
145
- return /^https?:\/\//.test(url);
146
- };
147
- // 动态计算图片路径
148
- const finalSrc = computed(() => {
149
- if (!props.src) return '';
150
- return isHttpOrHttps(props.src) ? props.src : config.VImage.prefix + props.src;
151
- });
152
-
153
- const finalPreviewSrc = computed(() => {
154
- if (!props.src) return '';
155
- return isHttpOrHttps(props.src) ? props.src : config.VImage.prefix + props.src;
156
- });
157
-
158
- onMounted(() => {
159
- // 判断当前环境
160
- if (process.env.VUE_APP_PLATFORM === 'app-plus') {
161
- isAppPlus.value = true;
162
- } else {
163
- isAppPlus.value = false;
164
- }
165
-
166
- if (props.lazy && props.src && window) {
167
- window.addEventListener('scroll', handleScroll);
168
- handleScroll();
169
- } else {
170
- displaySrc.value = finalSrc.value;
171
- }
172
- });
173
-
174
- const handleScroll = () => {
175
- if (!props.lazy || !props.src || !imgElement.value) return;
176
-
177
- const rect = imgElement.value.getBoundingClientRect();
178
- const isVisible = rect.top < (window.innerHeight || document.documentElement.clientHeight) && rect.bottom > 0;
179
-
180
- if (isVisible) {
181
- loadImage();
182
- }
183
- };
184
-
185
- const loadImage = () => {
186
- clearTimeout(lazyLoadTimer);
187
- lazyLoadTimer = setTimeout(() => {
188
- displaySrc.value = finalSrc.value;
189
- if (displaySrc.value && displaySrc.value.trim()) {
190
- isLoading.value = false
191
- isError.value = false;
192
- }
193
- }, props.delay);
194
- };
195
-
196
- const handleLoad = () => {
197
- isLoading.value = false;
198
- emit('handleLoading');
199
- retryCount.value = 0; // 成功后重置重试计数
200
- };
201
-
202
- const handleRetry = () => {
203
- if (retryCount.value < maxRetryCount.value) {
204
- retryCount.value++;
205
- displaySrc.value = null;
206
- isLoading.value = true;
207
- isError.value = false;
208
- loadImage();
209
- emit('handError', retryCount.value);
210
- } else {
211
- emit('handError', retryCount.value);
212
- }
213
- };
214
-
215
- const handleError = () => {
216
- isLoading.value = false;
217
- isError.value = true;
218
- handleRetry();
219
- };
220
-
221
- const handlePreview = () => {
222
- if (props.enablePreview && props.src && !props.disabled) {
223
- showPreview.value = true;
224
- }
225
- };
226
-
227
- const handleClick = () => {
228
- emit('click');
229
- };
230
-
231
- const closePreview = () => {
232
- showPreview.value = false;
233
- };
234
-
235
- watch(
236
- () => showPreview.value,
237
- (newValue) => {
238
- emit('update:enablePreview', newValue);
239
- }
240
- );
241
-
242
- watch(
243
- () => isLoading.value,
244
- () => {
245
- emit('update:loading', isLoading.value);
246
- }
247
- );
248
-
249
- watch(
250
- () => props.src,
251
- (newSrc) => {
252
- if (!props.lazy) {
253
- displaySrc.value = finalSrc.value;
254
- if (newSrc) {
255
- isLoading.value = true;
256
- isError.value = false;
257
- }
258
- } else {
259
- if (imgElement.value && window) {
260
- loadImage();
261
- }
262
- }
263
- }
264
- );
265
-
266
- onUnmounted(() => {
267
- clearTimeout(lazyLoadTimer);
268
- if (window) {
269
- window.removeEventListener('scroll', handleScroll);
270
- }
271
- });
272
- </script>
273
-
274
- <style lang="scss" scoped>
275
- .v-image-container {
276
- width: 100%;
277
- height: 100%;
278
- display: flex;
279
- justify-content: center;
280
- align-items: center;
281
- position: relative;
282
- }
283
-
284
- .v-image {
285
- max-width: 100%;
286
- max-height: 100%;
287
- width: v-bind('props.width');
288
- height: v-bind('props.height');
289
- object-fit: v-bind('fit');
290
-
291
- &--circle {
292
- border-radius: 50%;
293
- }
294
-
295
- &--rounded {
296
- border-radius: v-bind('props.borderRadius');
297
- }
298
-
299
- &--square {
300
- border-radius: 0;
301
- }
302
-
303
- &--loading,
304
- &--disabled {
305
- opacity: v-bind('config.opacity.disabled');
306
- }
307
- }
308
-
309
- .v-image-preview__img {
310
- max-width: 90%;
311
- max-height: 90%;
312
- object-fit: v-bind('previewFit');
313
-
314
- &--circle {
315
- border-radius: 50%;
316
- }
317
-
318
- &--rounded {
319
- border-radius: v-bind('props.previewBorderRadius');
320
- }
321
-
322
- &--square {
323
- border-radius: 0;
324
- }
325
- }
326
-
327
- .v-image-loading {
328
- position: absolute;
329
- top: 0;
330
- left: 0;
331
- width: 100%;
332
- height: 100%;
333
- display: flex;
334
- justify-content: center;
335
- align-items: center;
336
- z-index: 1;
337
- }
338
-
339
- .v-image-error {
340
- position: absolute;
341
- top: 0;
342
- left: 0;
343
- width: 100%;
344
- height: 100%;
345
- display: flex;
346
- flex-direction: column;
347
- justify-content: center;
348
- align-items: center;
349
- z-index: 1;
350
-
351
- .error-text {
352
- font-size: v-bind('config.fontSize.smallText');
353
- color: v-bind('config.fontColor.subTitle');
354
- margin-top: 12rpx;
355
- }
356
- }
357
- </style>
@@ -1,101 +0,0 @@
1
- <template>
2
- <v-input
3
- :value="displayValue"
4
- v-bind="$props"
5
- class="desensitize-input"
6
- @update:value="onInput"
7
- @focus="emit('focus', $event)"
8
- @blur="emit('blur', $event)"
9
- @confirm="emit('confirm', $event)"
10
- >
11
- <template #left><slot name="left" /></template>
12
- <template #right><slot name="right" /></template>
13
- </v-input>
14
- </template>
15
-
16
- <script setup lang="ts">
17
- import { ref, watch, computed } from 'vue';
18
-
19
- type BuiltInRule = 'name' | 'phone' | 'idcard';
20
- type CustomRule = (raw: string) => string;
21
- type Rule = BuiltInRule | CustomRule;
22
-
23
- const props = defineProps({
24
- modelValue: { type: String, default: '' },
25
- size: { type: String, default: 'medium' },
26
- placeholder: { type: [String, Array], default: '' },
27
- maxlength: { type: Number, default: null },
28
- disabled: { type: Boolean, default: false },
29
- showCounter: { type: Boolean, default: false },
30
- borderModel: { type: String, default: 'all' },
31
- combinationConfig: { type: Object, default: () => ({ isShow: false }) },
32
- autoFocus: { type: Boolean, default: false },
33
- realType: { type: String, default: 'text' },
34
- desensitize: { type: Boolean, default: true },
35
- rule: { type: [String, Function] as () => Rule, default: 'phone' }
36
- });
37
-
38
- const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'confirm']);
39
-
40
- /* ========= 内置规则 ========= */
41
- const builtInRules: Record<BuiltInRule, (v: string) => string> = {
42
- /* 姓名:保留首字母和末字母,中间全星号 */
43
- name: (v) => (v.length <= 2 ? v.slice(-1).padStart(v.length, '*') : v[0] + '*'.repeat(v.length - 2) + v.slice(-1)),
44
-
45
- /* 手机:保留前三后四,中间星号 */
46
- phone: (v) => {
47
- const digits = v.replace(/\D/g, '');
48
- if (digits.length < 7) return v;
49
-
50
- let result = '';
51
- let digitIndex = 0;
52
-
53
- for (let i = 0; i < v.length; i++) {
54
- if (/\d/.test(v[i])) {
55
- if (digitIndex < 3) {
56
- result += v[i];
57
- } else if (digitIndex >= digits.length - 4) {
58
- result += v[i];
59
- } else {
60
- result += '*';
61
- }
62
- digitIndex++;
63
- } else {
64
- result += v[i];
65
- }
66
- }
67
- return result;
68
- },
69
-
70
- /* 身份证:保留前 4 后 4,中间全星号 */
71
- idcard: (v) => {
72
- const len = v.length;
73
- if (len < 8) return v;
74
- return v.slice(0, 4) + '*'.repeat(len - 8) + v.slice(-4);
75
- }
76
- };
77
-
78
- /* ========= 脱敏显示值 ========= */
79
- const displayValue = computed(() => {
80
- if (!props.desensitize) return props.modelValue;
81
-
82
- const rule = props.rule;
83
- if (typeof rule === 'function') {
84
- return rule(props.modelValue);
85
- }
86
- return builtInRules[rule as BuiltInRule](props.modelValue);
87
- });
88
-
89
- /* ========= 输入事件:直接传递原始值 ========= */
90
- const onInput = (val: string) => {
91
- // 注意:这里传入的val是脱敏后的显示值,我们需要直接更新原始值
92
- emit('update:modelValue', val);
93
- };
94
- </script>
95
-
96
- <style scoped>
97
- .desensitize-input :deep(input) {
98
- font-family: monospace;
99
- line-height: 1.5;
100
- }
101
- </style>
@@ -1,11 +0,0 @@
1
- <template>
2
- <view>
3
-
4
- </view>
5
- </template>
6
-
7
- <script lang="ts" setup>
8
- </script>
9
-
10
- <style lang="scss" scoped>
11
- </style>