@tdesign/uniapp 0.8.0 → 0.8.1
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/CHANGELOG.md +11 -0
- package/README.md +43 -34
- package/dist/action-sheet/README.md +8 -3
- package/dist/avatar/README.md +3 -1
- package/dist/back-top/README.md +3 -1
- package/dist/badge/README.md +3 -1
- package/dist/button/README.md +3 -1
- package/dist/calendar/README.md +3 -1
- package/dist/cascader/README.md +3 -1
- package/dist/cell/README.md +3 -1
- package/dist/checkbox/README.md +3 -1
- package/dist/col/README.md +3 -1
- package/dist/collapse/README.md +3 -1
- package/dist/color-picker/README.md +3 -1
- package/dist/common/common.ts +121 -5
- package/dist/common/src/index.js +0 -1
- package/dist/common/validator.ts +496 -0
- package/dist/config-provider/README.md +3 -1
- package/dist/count-down/README.md +3 -1
- package/dist/date-time-picker/README.md +3 -1
- package/dist/dialog/README.md +3 -1
- package/dist/divider/README.md +3 -1
- package/dist/drawer/README.md +3 -1
- package/dist/dropdown-menu/README.md +2 -2
- package/dist/empty/README.md +3 -1
- package/dist/fab/README.md +3 -1
- package/dist/footer/README.md +3 -1
- package/dist/form/README.en-US.md +2 -2
- package/dist/form/README.md +5 -3
- package/dist/form/form.vue +1 -1
- package/dist/form/type.ts +3 -3
- package/dist/form-item/form-item.css +4 -0
- package/dist/form-item/form-item.vue +2 -3
- package/dist/form-item/type.ts +2 -2
- package/dist/grid/README.md +3 -1
- package/dist/guide/README.md +3 -1
- package/dist/icon/README.md +10 -7
- package/dist/icon/icon.css +1633 -1624
- package/dist/image/README.md +3 -1
- package/dist/image-viewer/README.md +3 -1
- package/dist/index.js +13 -0
- package/dist/indexes/README.md +3 -1
- package/dist/input/README.md +3 -1
- package/dist/input/input.vue +1 -27
- package/dist/link/README.md +3 -1
- package/dist/loading/README.md +3 -1
- package/dist/message/README.md +8 -3
- package/dist/mixins/page-scroll.d.ts +19 -0
- package/dist/mixins/skyline.js +1 -1
- package/dist/navbar/README.md +3 -1
- package/dist/notice-bar/README.md +3 -1
- package/dist/overlay/README.md +3 -1
- package/dist/picker/README.md +3 -1
- package/dist/popover/README.md +3 -1
- package/dist/popup/README.md +3 -1
- package/dist/progress/README.md +2 -2
- package/dist/pull-down-refresh/README.md +3 -1
- package/dist/qrcode/README.md +3 -1
- package/dist/radio/README.md +3 -1
- package/dist/rate/README.md +3 -1
- package/dist/rate/rate.css +1 -0
- package/dist/result/README.md +3 -1
- package/dist/script/postinstall.js +49 -24
- package/dist/search/README.md +3 -1
- package/dist/side-bar/README.md +3 -1
- package/dist/skeleton/README.md +3 -1
- package/dist/slider/README.md +3 -1
- package/dist/stepper/README.md +3 -1
- package/dist/steps/README.md +3 -1
- package/dist/sticky/README.md +3 -1
- package/dist/swipe-cell/README.md +3 -1
- package/dist/swiper/README.md +3 -1
- package/dist/switch/README.md +3 -1
- package/dist/tab-bar/README.md +3 -1
- package/dist/tabs/README.md +3 -1
- package/dist/tag/README.md +3 -1
- package/dist/textarea/README.md +3 -1
- package/dist/textarea/textarea.vue +1 -25
- package/dist/theme.css +467 -0
- package/dist/theme.css.d.ts +2 -0
- package/dist/theme.less +1 -0
- package/dist/theme.less.d.ts +2 -0
- package/dist/toast/README.md +3 -1
- package/dist/transition/README.md +4 -6
- package/dist/tree-select/README.md +3 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/upload/README.md +3 -1
- package/dist/watermark/README.md +3 -1
- package/package.json +37 -3
- package/dist/common/src/superComponent.js +0 -5
- package/dist/common/validator.js +0 -210
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import type { IsEmailOptions, IsURLOptions } from './common';
|
|
2
|
+
|
|
3
|
+
export function isFunction(val: unknown): val is (...args: unknown[]) => unknown {
|
|
4
|
+
return typeof val === 'function';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const isString = (val: unknown): val is string => typeof val === 'string';
|
|
8
|
+
|
|
9
|
+
export const isNull = (value: unknown): value is null => value === null;
|
|
10
|
+
|
|
11
|
+
export const isUndefined = (value: unknown): value is undefined => value === undefined;
|
|
12
|
+
|
|
13
|
+
export function isDef<T>(value: T | undefined | null): value is T {
|
|
14
|
+
return !isUndefined(value) && !isNull(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isInteger(value: unknown): boolean {
|
|
18
|
+
return Number.isInteger(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isNumeric(value: unknown): boolean {
|
|
22
|
+
return !Number.isNaN(Number(value));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isNumber(value: unknown): value is number {
|
|
26
|
+
return typeof value === 'number';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isBoolean(value: unknown): value is boolean {
|
|
30
|
+
return typeof value === 'boolean';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isObject(x: unknown): x is Record<string, unknown> {
|
|
34
|
+
const type = typeof x;
|
|
35
|
+
return x !== null && (type === 'object' || type === 'function');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isPlainObject(val: unknown): val is Record<string, unknown> {
|
|
39
|
+
return val !== null && typeof val === 'object' && Object.prototype.toString.call(val) === '[object Object]';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isEmpty(val: unknown): boolean {
|
|
43
|
+
if (val === null || val === undefined) return true;
|
|
44
|
+
if (typeof val === 'string' || Array.isArray(val)) return val.length === 0;
|
|
45
|
+
if (val instanceof Map || val instanceof Set) return val.size === 0;
|
|
46
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 日期校验选项
|
|
52
|
+
*/
|
|
53
|
+
export interface IsDateOptions {
|
|
54
|
+
/**
|
|
55
|
+
* 日期格式
|
|
56
|
+
* @default 'YYYY/MM/DD'
|
|
57
|
+
*/
|
|
58
|
+
format?: string;
|
|
59
|
+
/**
|
|
60
|
+
* 日期分隔符
|
|
61
|
+
* @default ['/', '-']
|
|
62
|
+
*/
|
|
63
|
+
delimiters?: string[];
|
|
64
|
+
/**
|
|
65
|
+
* 是否启用严格模式
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
strictMode?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 验证是否为有效日期
|
|
73
|
+
* 支持字符串格式(YYYY/MM/DD、YYYY-MM-DD 等)和 Date 对象
|
|
74
|
+
*/
|
|
75
|
+
export function isDate(input: string | Date, options?: IsDateOptions): boolean {
|
|
76
|
+
const defaultOptions: Required<IsDateOptions> = {
|
|
77
|
+
format: 'YYYY/MM/DD',
|
|
78
|
+
delimiters: ['/', '-'],
|
|
79
|
+
strictMode: false,
|
|
80
|
+
};
|
|
81
|
+
const opts = { ...defaultOptions, ...options };
|
|
82
|
+
|
|
83
|
+
if (typeof input === 'string') {
|
|
84
|
+
const delimiter = opts.delimiters.find(d => opts.format.includes(d));
|
|
85
|
+
if (!delimiter) return false;
|
|
86
|
+
|
|
87
|
+
const formatParts = opts.format.split(delimiter);
|
|
88
|
+
const dateParts = input.split(delimiter);
|
|
89
|
+
if (formatParts.length !== dateParts.length) return false;
|
|
90
|
+
|
|
91
|
+
let year = '';
|
|
92
|
+
let month = '';
|
|
93
|
+
let day = '';
|
|
94
|
+
for (let i = 0; i < formatParts.length; i += 1) {
|
|
95
|
+
const fmt = formatParts[i].toUpperCase();
|
|
96
|
+
const val = dateParts[i];
|
|
97
|
+
if (fmt.includes('Y')) year = val;
|
|
98
|
+
else if (fmt.includes('M')) month = val;
|
|
99
|
+
else if (fmt.includes('D')) day = val;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (month.length === 1) month = `0${month}`;
|
|
103
|
+
if (day.length === 1) day = `0${day}`;
|
|
104
|
+
|
|
105
|
+
if (year.length === 2) {
|
|
106
|
+
const currentYearSuffix = new Date().getFullYear() % 100;
|
|
107
|
+
year = Number(year) <= currentYearSuffix ? `20${year}` : `19${year}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const date = new Date(`${year}-${month}-${day}T00:00:00.000Z`);
|
|
111
|
+
return (
|
|
112
|
+
date.getUTCFullYear() === Number(year)
|
|
113
|
+
&& date.getUTCMonth() + 1 === Number(month)
|
|
114
|
+
&& date.getUTCDate() === Number(day)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!opts.strictMode) {
|
|
119
|
+
if (Object.prototype.toString.call(input) === '[object Date]' && Number.isFinite((input as Date).getTime())) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 检查主机名是否匹配黑/白名单
|
|
129
|
+
*/
|
|
130
|
+
function checkHost(host: string, list: Array<string | RegExp>): boolean {
|
|
131
|
+
if (!list?.length) return false;
|
|
132
|
+
return list.some((item) => {
|
|
133
|
+
if (item instanceof RegExp) return item.test(host);
|
|
134
|
+
return host === item;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 计算字符串的字节长度(UTF-8 编码)
|
|
140
|
+
*/
|
|
141
|
+
function byteLength(str: string): number {
|
|
142
|
+
return encodeURI(str).split(/%..|./).length - 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* FQDN 校验选项
|
|
147
|
+
*/
|
|
148
|
+
interface IsFQDNOptions {
|
|
149
|
+
/**
|
|
150
|
+
* 是否要求顶级域名
|
|
151
|
+
* @default true
|
|
152
|
+
*/
|
|
153
|
+
require_tld?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* 是否允许下划线
|
|
156
|
+
* @default false
|
|
157
|
+
*/
|
|
158
|
+
allow_underscores?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* 是否允许末尾的点
|
|
161
|
+
* @default false
|
|
162
|
+
*/
|
|
163
|
+
allow_trailing_dot?: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 检查是否为 FQDN(完全限定域名)
|
|
168
|
+
*/
|
|
169
|
+
function isFQDN(str: string, options: IsFQDNOptions = {}): boolean {
|
|
170
|
+
const opts: Required<IsFQDNOptions> = {
|
|
171
|
+
require_tld: true,
|
|
172
|
+
allow_underscores: false,
|
|
173
|
+
allow_trailing_dot: false,
|
|
174
|
+
...options,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
let domain = str;
|
|
178
|
+
if (opts.allow_trailing_dot && domain[domain.length - 1] === '.') {
|
|
179
|
+
domain = domain.substring(0, domain.length - 1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const parts = domain.split('.');
|
|
183
|
+
const tld = parts[parts.length - 1];
|
|
184
|
+
|
|
185
|
+
if (opts.require_tld) {
|
|
186
|
+
if (parts.length < 2) return false;
|
|
187
|
+
if (!/^([a-z\u00A1-\u00A8\u00AA-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (/\s/.test(tld)) return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (/^\d+$/.test(tld)) return false;
|
|
194
|
+
|
|
195
|
+
return parts.every((part) => {
|
|
196
|
+
if (part.length > 63) return false;
|
|
197
|
+
if (!/^[a-z_\u00a1-\uffff0-9-]+$/i.test(part)) return false;
|
|
198
|
+
// 禁止全角字符
|
|
199
|
+
if (/[\uff01-\uff5e]/.test(part)) return false;
|
|
200
|
+
// 不能以连字符开头或结尾
|
|
201
|
+
if (/^-|-$/.test(part)) return false;
|
|
202
|
+
if (!opts.allow_underscores && /_/.test(part)) return false;
|
|
203
|
+
return true;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 检查是否为有效的 IPv4 或 IPv6 地址
|
|
209
|
+
*/
|
|
210
|
+
function isIP(ipAddress: string, version?: number): boolean {
|
|
211
|
+
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
|
|
212
|
+
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
|
|
213
|
+
const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`);
|
|
214
|
+
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
|
|
215
|
+
const IPv6AddressRegExp = new RegExp('^('
|
|
216
|
+
+ `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|`
|
|
217
|
+
+ `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|`
|
|
218
|
+
+ `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|`
|
|
219
|
+
+ `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|`
|
|
220
|
+
+ `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|`
|
|
221
|
+
+ `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|`
|
|
222
|
+
+ `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|`
|
|
223
|
+
+ `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))`
|
|
224
|
+
+ ')(%[0-9a-zA-Z.]{1,})?$');
|
|
225
|
+
|
|
226
|
+
const v = version ? version.toString() : '';
|
|
227
|
+
if (!v) {
|
|
228
|
+
return isIP(ipAddress, 4) || isIP(ipAddress, 6);
|
|
229
|
+
}
|
|
230
|
+
if (v === '4') return IPv4AddressRegExp.test(ipAddress);
|
|
231
|
+
if (v === '6') return IPv6AddressRegExp.test(ipAddress);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 验证是否为有效邮箱地址
|
|
237
|
+
* 参考 validator.js 的 isEmail 实现,支持 options 参数
|
|
238
|
+
*/
|
|
239
|
+
export function isEmail(str: string, options?: IsEmailOptions): boolean {
|
|
240
|
+
if (typeof str !== 'string') return false;
|
|
241
|
+
|
|
242
|
+
let email = str;
|
|
243
|
+
|
|
244
|
+
const defaultOpts: Required<IsEmailOptions> = {
|
|
245
|
+
allow_display_name: false,
|
|
246
|
+
allow_underscores: false,
|
|
247
|
+
require_display_name: false,
|
|
248
|
+
allow_utf8_local_part: true,
|
|
249
|
+
require_tld: true,
|
|
250
|
+
blacklisted_chars: '',
|
|
251
|
+
ignore_max_length: false,
|
|
252
|
+
host_blacklist: [],
|
|
253
|
+
host_whitelist: [],
|
|
254
|
+
allow_ip_domain: false,
|
|
255
|
+
domain_specific_validation: false,
|
|
256
|
+
};
|
|
257
|
+
const opts = { ...defaultOpts, ...options };
|
|
258
|
+
|
|
259
|
+
// 处理 Display Name 格式,如 "John" <john@example.com>
|
|
260
|
+
// eslint-disable-next-line no-control-regex
|
|
261
|
+
const splitNameAddress = /^([^\x00-\x1F\x7F-\x9F\cX]+)</i;
|
|
262
|
+
if (opts.require_display_name || opts.allow_display_name) {
|
|
263
|
+
const displayEmail = email.match(splitNameAddress);
|
|
264
|
+
if (displayEmail) {
|
|
265
|
+
let displayName = displayEmail[1];
|
|
266
|
+
email = email.replace(displayName, '').replace(/(^<|>$)/g, '');
|
|
267
|
+
if (displayName.endsWith(' ')) {
|
|
268
|
+
displayName = displayName.slice(0, -1);
|
|
269
|
+
}
|
|
270
|
+
// 简单校验 display name
|
|
271
|
+
const nameWithoutQuotes = displayName.replace(/^"(.+)"$/, '$1');
|
|
272
|
+
if (!nameWithoutQuotes.trim()) return false;
|
|
273
|
+
if (/[.";<>]/.test(nameWithoutQuotes) && nameWithoutQuotes === displayName) return false;
|
|
274
|
+
} else if (opts.require_display_name) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!opts.ignore_max_length && email.length > 254) return false;
|
|
280
|
+
|
|
281
|
+
const parts = email.split('@');
|
|
282
|
+
const domain = parts.pop()!;
|
|
283
|
+
const user = parts.join('@');
|
|
284
|
+
|
|
285
|
+
if (!user || !domain) return false;
|
|
286
|
+
|
|
287
|
+
const lowerDomain = domain.toLowerCase();
|
|
288
|
+
|
|
289
|
+
// 域名黑名单
|
|
290
|
+
if (opts.host_blacklist.length > 0 && checkHost(lowerDomain, opts.host_blacklist)) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
// 域名白名单
|
|
294
|
+
if (opts.host_whitelist.length > 0 && !checkHost(lowerDomain, opts.host_whitelist)) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Gmail 特殊校验
|
|
299
|
+
if (opts.domain_specific_validation && (lowerDomain === 'gmail.com' || lowerDomain === 'googlemail.com')) {
|
|
300
|
+
const username = user.toLowerCase().split('+')[0];
|
|
301
|
+
if (byteLength(username.replace(/\./g, '')) < 6 || byteLength(username.replace(/\./g, '')) > 30) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
const gmailUserPart = /^[a-z\d]+$/;
|
|
305
|
+
const gmailParts = username.split('.');
|
|
306
|
+
for (let i = 0; i < gmailParts.length; i++) {
|
|
307
|
+
if (!gmailUserPart.test(gmailParts[i])) return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 字节长度校验
|
|
312
|
+
if (!opts.ignore_max_length && (byteLength(user) > 64 || byteLength(domain) > 254)) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 域名校验:先尝试 FQDN,再尝试 IP
|
|
317
|
+
if (!isFQDN(domain, { require_tld: opts.require_tld, allow_underscores: opts.allow_underscores })) {
|
|
318
|
+
if (!opts.allow_ip_domain) return false;
|
|
319
|
+
if (!isIP(domain)) {
|
|
320
|
+
if (!domain.startsWith('[') || !domain.endsWith(']')) return false;
|
|
321
|
+
const noBracketDomain = domain.slice(1, -1);
|
|
322
|
+
if (!noBracketDomain || !isIP(noBracketDomain)) return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 黑名单字符
|
|
327
|
+
if (opts.blacklisted_chars && new RegExp(`[${opts.blacklisted_chars}]+`, 'g').test(user)) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 引号包裹的用户名
|
|
332
|
+
if (user[0] === '"' && user[user.length - 1] === '"') {
|
|
333
|
+
const innerUser = user.slice(1, user.length - 1);
|
|
334
|
+
/* eslint-disable no-control-regex */
|
|
335
|
+
if (opts.allow_utf8_local_part) {
|
|
336
|
+
return /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i.test(innerUser);
|
|
337
|
+
}
|
|
338
|
+
return /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i.test(innerUser);
|
|
339
|
+
/* eslint-enable no-control-regex */
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 用户名逐段校验(按 . 分割)
|
|
343
|
+
/* eslint-disable no-useless-escape */
|
|
344
|
+
const pattern = opts.allow_utf8_local_part
|
|
345
|
+
? /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A1-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i
|
|
346
|
+
: /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i;
|
|
347
|
+
/* eslint-enable no-useless-escape */
|
|
348
|
+
const userParts = user.split('.');
|
|
349
|
+
for (let i = 0; i < userParts.length; i++) {
|
|
350
|
+
if (!pattern.test(userParts[i])) return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 验证是否为有效 URL
|
|
358
|
+
* 参考 validator.js 的 isURL 实现,支持完整 options 参数
|
|
359
|
+
*/
|
|
360
|
+
export function isURL(str: string, options?: IsURLOptions): boolean {
|
|
361
|
+
if (typeof str !== 'string') return false;
|
|
362
|
+
if (!str || /[\s<>]/.test(str)) return false;
|
|
363
|
+
if (str.indexOf('mailto:') === 0) return false;
|
|
364
|
+
|
|
365
|
+
const defaultOpts: Required<IsURLOptions> = {
|
|
366
|
+
protocols: ['http', 'https', 'ftp'],
|
|
367
|
+
require_tld: true,
|
|
368
|
+
require_protocol: false,
|
|
369
|
+
require_host: true,
|
|
370
|
+
require_port: false,
|
|
371
|
+
require_valid_protocol: true,
|
|
372
|
+
allow_underscores: false,
|
|
373
|
+
allow_trailing_dot: false,
|
|
374
|
+
allow_protocol_relative_urls: false,
|
|
375
|
+
allow_fragments: true,
|
|
376
|
+
allow_query_components: true,
|
|
377
|
+
disallow_auth: false,
|
|
378
|
+
validate_length: true,
|
|
379
|
+
max_allowed_length: 2084,
|
|
380
|
+
host_whitelist: [],
|
|
381
|
+
host_blacklist: [],
|
|
382
|
+
};
|
|
383
|
+
const opts = { ...defaultOpts, ...options };
|
|
384
|
+
|
|
385
|
+
if (opts.validate_length && opts.max_allowed_length && str.length > opts.max_allowed_length) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!opts.allow_fragments && str.includes('#')) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!opts.allow_query_components && (str.includes('?') || str.includes('&'))) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let url = str;
|
|
398
|
+
let protocol: string | undefined;
|
|
399
|
+
|
|
400
|
+
// 协议解析
|
|
401
|
+
const protocolRegex = /^([a-z][a-z0-9+\-.]*):\/\//i;
|
|
402
|
+
const protocolMatch = url.match(protocolRegex);
|
|
403
|
+
if (protocolMatch) {
|
|
404
|
+
protocol = protocolMatch[1].toLowerCase();
|
|
405
|
+
if (opts.require_valid_protocol && !opts.protocols.includes(protocol)) return false;
|
|
406
|
+
url = url.slice(protocolMatch[0].length);
|
|
407
|
+
} else if (opts.require_protocol) {
|
|
408
|
+
if (opts.allow_protocol_relative_urls && str.startsWith('//')) {
|
|
409
|
+
url = url.slice(2);
|
|
410
|
+
} else {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
} else if (str.startsWith('//')) {
|
|
414
|
+
if (!opts.allow_protocol_relative_urls) return false;
|
|
415
|
+
url = url.slice(2);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (url === '') return false;
|
|
419
|
+
|
|
420
|
+
// 分离片段和查询参数
|
|
421
|
+
let split: string[] = url.split('#');
|
|
422
|
+
url = split.shift()!;
|
|
423
|
+
split = url.split('?');
|
|
424
|
+
url = split.shift()!;
|
|
425
|
+
|
|
426
|
+
// 分离路径
|
|
427
|
+
split = url.split('/');
|
|
428
|
+
url = split.shift()!;
|
|
429
|
+
|
|
430
|
+
if (url === '' && !opts.require_host) return true;
|
|
431
|
+
|
|
432
|
+
// 分离认证信息
|
|
433
|
+
split = url.split('@');
|
|
434
|
+
if (split.length > 1) {
|
|
435
|
+
if (opts.disallow_auth) return false;
|
|
436
|
+
const auth = split.shift()!;
|
|
437
|
+
if (auth === '') return false;
|
|
438
|
+
if (auth.indexOf(':') >= 0 && auth.split(':').length > 2) return false;
|
|
439
|
+
const [authUser, authPassword] = auth.split(':');
|
|
440
|
+
if (authUser === '' && authPassword === '') return false;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const hostname = split.join('@');
|
|
444
|
+
let portStr: string | null = null;
|
|
445
|
+
let ipv6: string | null = null;
|
|
446
|
+
let host: string;
|
|
447
|
+
|
|
448
|
+
// IPv6 包裹格式 [::1]:port
|
|
449
|
+
const wrappedIpv6 = /^\[([^\]]+)\](?::([0-9]+))?$/;
|
|
450
|
+
const ipv6Match = hostname.match(wrappedIpv6);
|
|
451
|
+
if (ipv6Match) {
|
|
452
|
+
host = '';
|
|
453
|
+
[, ipv6] = ipv6Match;
|
|
454
|
+
portStr = ipv6Match[2] || null;
|
|
455
|
+
} else {
|
|
456
|
+
const hostSplit = hostname.split(':');
|
|
457
|
+
[host] = hostSplit;
|
|
458
|
+
const restParts = hostSplit.slice(1);
|
|
459
|
+
if (restParts.length) {
|
|
460
|
+
portStr = restParts.join(':');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 端口校验
|
|
465
|
+
if (portStr !== null && portStr.length > 0) {
|
|
466
|
+
const port = parseInt(portStr, 10);
|
|
467
|
+
if (!/^[0-9]+$/.test(portStr) || port <= 0 || port > 65535) return false;
|
|
468
|
+
} else if (opts.require_port) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 白名单优先
|
|
473
|
+
if (opts?.host_whitelist?.length) {
|
|
474
|
+
return checkHost(host, opts.host_whitelist);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (host === '' && !opts.require_host) return true;
|
|
478
|
+
|
|
479
|
+
// 域名校验
|
|
480
|
+
if (!isIP(host) && !isFQDN(host, {
|
|
481
|
+
require_tld: opts.require_tld,
|
|
482
|
+
allow_underscores: opts.allow_underscores,
|
|
483
|
+
allow_trailing_dot: opts.allow_trailing_dot,
|
|
484
|
+
}) && (!ipv6 || !isIP(ipv6, 6))) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
host = host || ipv6!;
|
|
489
|
+
|
|
490
|
+
// 黑名单
|
|
491
|
+
if (opts?.host_blacklist?.length && checkHost(host, opts.host_blacklist)) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
@@ -8,7 +8,9 @@ isComponent: true
|
|
|
8
8
|
|
|
9
9
|
## 引入
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-config-provider />`。详细配置请参考 [快速开始](../getting-started)。
|
|
12
|
+
|
|
13
|
+
如需手动引入:
|
|
12
14
|
|
|
13
15
|
```js
|
|
14
16
|
import TConfigProvider from '@tdesign/uniapp/config-provider/config-provider.vue';
|
|
@@ -12,7 +12,9 @@ isComponent: true
|
|
|
12
12
|
|
|
13
13
|
## 引入
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-count-down />`。详细配置请参考 [快速开始](../getting-started)。
|
|
16
|
+
|
|
17
|
+
如需手动引入:
|
|
16
18
|
|
|
17
19
|
```js
|
|
18
20
|
import TCountDown from '@tdesign/uniapp/count-down/count-down.vue';
|
|
@@ -8,7 +8,9 @@ isComponent: true
|
|
|
8
8
|
|
|
9
9
|
## 引入
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-date-time-picker />`。详细配置请参考 [快速开始](../getting-started)。
|
|
12
|
+
|
|
13
|
+
如需手动引入:
|
|
12
14
|
|
|
13
15
|
```js
|
|
14
16
|
import TDateTimePicker from '@tdesign/uniapp/date-time-picker/date-time-picker.vue';
|
package/dist/dialog/README.md
CHANGED
package/dist/divider/README.md
CHANGED
package/dist/drawer/README.md
CHANGED
|
@@ -9,9 +9,9 @@ isComponent: true
|
|
|
9
9
|
|
|
10
10
|
## 引入
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-dropdown-menu />` 和 `<t-dropdown-item />`。详细配置请参考 [快速开始](../getting-started)。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
如需手动引入:
|
|
15
15
|
|
|
16
16
|
```js
|
|
17
17
|
import DropdownMenu from '@tdesign/uniapp/dropdown-menu/dropdown-menu.vue';
|
package/dist/empty/README.md
CHANGED
package/dist/fab/README.md
CHANGED
package/dist/footer/README.md
CHANGED
|
@@ -81,7 +81,7 @@ name | type | default | description | required
|
|
|
81
81
|
-- | -- | -- | -- | --
|
|
82
82
|
boolean | Boolean | - | \- | N
|
|
83
83
|
date | Boolean / Object | - | Typescript: `boolean \| IsDateOptions` `interface IsDateOptions { format: string; strictMode: boolean; delimiters: string[] }`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
84
|
-
email | Boolean / Object | - | Typescript: `boolean \| IsEmailOptions
|
|
84
|
+
email | Boolean / Object | - | Typescript: `boolean \| IsEmailOptions`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
85
85
|
enum | Array | - | Typescript: `Array<string>` | N
|
|
86
86
|
idcard | Boolean | - | \- | N
|
|
87
87
|
len | Number / Boolean | - | \- | N
|
|
@@ -94,7 +94,7 @@ required | Boolean | - | \- | N
|
|
|
94
94
|
telnumber | Boolean | - | \- | N
|
|
95
95
|
trigger | String | change | Typescript: `ValidateTriggerType` | N
|
|
96
96
|
type | String | error | options: error/warning | N
|
|
97
|
-
url | Boolean / Object | - | Typescript: `boolean \| IsURLOptions
|
|
97
|
+
url | Boolean / Object | - | Typescript: `boolean \| IsURLOptions`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
98
98
|
validator | Function | - | Typescript: `CustomValidator` `type CustomValidator = (val: ValueType, context?: { formData: Data , name: string }) => CustomValidateResolveType \| Promise<CustomValidateResolveType>` `type CustomValidateResolveType = boolean \| CustomValidateObj` `interface CustomValidateObj { result: boolean; message: string; type?: 'error' \| 'warning' \| 'success' }` `type ValueType = any`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
99
99
|
whitespace | Boolean | - | \- | N
|
|
100
100
|
|
package/dist/form/README.md
CHANGED
|
@@ -8,7 +8,9 @@ toc: false
|
|
|
8
8
|
|
|
9
9
|
## 引入
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-form />`。详细配置请参考 [快速开始](../getting-started)。
|
|
12
|
+
|
|
13
|
+
如需手动引入:
|
|
12
14
|
|
|
13
15
|
```js
|
|
14
16
|
import TForm from '@tdesign/uniapp/form/form.vue';
|
|
@@ -90,7 +92,7 @@ label | 自定义 `label` 显示内容
|
|
|
90
92
|
-- | -- | -- | -- | --
|
|
91
93
|
boolean | Boolean | - | 内置校验方法,校验值类型是否为布尔类型,示例:`{ boolean: true, message: '数据类型必须是布尔类型' }` | N
|
|
92
94
|
date | Boolean / Object | - | 内置校验方法,校验值是否为日期格式,[参数文档](https://github.com/validatorjs/validator.js),示例:`{ date: { delimiters: '-' }, message: '日期分隔线必须是短横线(-)' }`。TS 类型:`boolean \| IsDateOptions` `interface IsDateOptions { format: string; strictMode: boolean; delimiters: string[] }`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
93
|
-
email | Boolean / Object | - | 内置校验方法,校验值是否为邮件格式,[参数文档](https://github.com/validatorjs/validator.js),示例:`{ email: { ignore_max_length: true }, message: '请输入正确的邮箱地址' }`。TS 类型:`boolean \| IsEmailOptions
|
|
95
|
+
email | Boolean / Object | - | 内置校验方法,校验值是否为邮件格式,[参数文档](https://github.com/validatorjs/validator.js),示例:`{ email: { ignore_max_length: true }, message: '请输入正确的邮箱地址' }`。TS 类型:`boolean \| IsEmailOptions`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
94
96
|
enum | Array | - | 内置校验方法,校验值是否属于枚举值中的值。示例:`{ enum: ['primary', 'info', 'warning'], message: '值只能是 primary/info/warning 中的一种' }`。TS 类型:`Array<string>` | N
|
|
95
97
|
idcard | Boolean | - | 内置校验方法,校验值是否为身份证号码,组件校验正则为 `/^(\\d{18,18}\|\\d{15,15}\|\\d{17,17}x)$/i`,示例:`{ idcard: true, message: '请输入正确的身份证号码' }` | N
|
|
96
98
|
len | Number / Boolean | - | 内置校验方法,校验值固定长度,如:len: 10 表示值的字符长度只能等于 10 ,中文表示 2 个字符,英文为 1 个字符。示例:`{ len: 10, message: '内容长度不对' }`。<br />如果希望字母和中文都是同样的长度,示例:`{ validator: (val) => val.length === 10, message: '内容文本长度只能是 10 个字' }` | N
|
|
@@ -103,7 +105,7 @@ required | Boolean | - | 内置校验方法,校验值是否已经填写。该
|
|
|
103
105
|
telnumber | Boolean | - | 内置校验方法,校验值是否为手机号码,校验正则为 `/^1[3-9]\d{9}$/`,示例:`{ telnumber: true, message: '请输入正确的手机号码' }` | N
|
|
104
106
|
trigger | String | change | 校验触发方式。TS 类型:`ValidateTriggerType` | N
|
|
105
107
|
type | String | error | 校验未通过时呈现的错误信息类型,有 告警信息提示 和 错误信息提示 等两种。可选项:error/warning | N
|
|
106
|
-
url | Boolean / Object | - | 内置校验方法,校验值是否为网络链接地址,[参数文档](https://github.com/validatorjs/validator.js),示例:`{ url: { protocols: ['http','https','ftp'] }, message: '请输入正确的 Url 地址' }`。TS 类型:`boolean \| IsURLOptions
|
|
108
|
+
url | Boolean / Object | - | 内置校验方法,校验值是否为网络链接地址,[参数文档](https://github.com/validatorjs/validator.js),示例:`{ url: { protocols: ['http','https','ftp'] }, message: '请输入正确的 Url 地址' }`。TS 类型:`boolean \| IsURLOptions`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
107
109
|
validator | Function | - | 自定义校验规则,context 中 formData 为当前完整表单值,name为该字段的标识,示例:`{ validator: (val) => val.length > 0, message: '请输入内容'}`。TS 类型:`CustomValidator` `type CustomValidator = (val: ValueType, context?: { formData: Data , name: string }) => CustomValidateResolveType \| Promise<CustomValidateResolveType>` `type CustomValidateResolveType = boolean \| CustomValidateObj` `interface CustomValidateObj { result: boolean; message: string; type?: 'error' \| 'warning' \| 'success' }` `type ValueType = any`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/form/type.ts) | N
|
|
108
110
|
whitespace | Boolean | - | 内置校验方法,校验值是否为空格。示例:`{ whitespace: true, message: '值不能为空' }` | N
|
|
109
111
|
|