@tdesign/uniapp 0.8.0 → 0.9.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 (134) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +43 -34
  3. package/dist/action-sheet/README.md +8 -3
  4. package/dist/avatar/README.md +3 -1
  5. package/dist/back-top/README.md +3 -1
  6. package/dist/badge/README.en-US.md +1 -0
  7. package/dist/badge/README.md +4 -1
  8. package/dist/badge/badge.css +1 -1
  9. package/dist/button/README.md +3 -1
  10. package/dist/button/button.vue +5 -0
  11. package/dist/calendar/README.md +3 -1
  12. package/dist/calendar/calendar.vue +6 -2
  13. package/dist/cascader/README.md +3 -1
  14. package/dist/cell/README.md +3 -1
  15. package/dist/checkbox/README.md +3 -1
  16. package/dist/col/README.md +3 -1
  17. package/dist/collapse/README.md +3 -1
  18. package/dist/color-picker/README.md +3 -1
  19. package/dist/common/common.ts +121 -5
  20. package/dist/common/src/index.js +0 -1
  21. package/dist/common/style/theme/index-light.css +282 -0
  22. package/dist/common/style/theme/index-light.less +9 -0
  23. package/dist/common/style/theme/raw/_components-light.less +8 -0
  24. package/dist/common/style/theme/raw/_light-only.less +181 -0
  25. package/dist/common/validator.ts +496 -0
  26. package/dist/config-provider/README.md +3 -1
  27. package/dist/count-down/README.md +3 -1
  28. package/dist/date-time-picker/README.md +3 -1
  29. package/dist/dialog/README.md +3 -1
  30. package/dist/divider/README.md +3 -1
  31. package/dist/drawer/README.md +3 -1
  32. package/dist/dropdown-item/dropdown-item.vue +2 -0
  33. package/dist/dropdown-menu/README.md +2 -2
  34. package/dist/dropdown-menu/dropdown-menu.vue +1 -1
  35. package/dist/empty/README.md +3 -1
  36. package/dist/fab/README.md +3 -1
  37. package/dist/fab/fab.vue +2 -2
  38. package/dist/fab/props.ts +1 -1
  39. package/dist/fab/type.ts +1 -1
  40. package/dist/footer/README.md +3 -1
  41. package/dist/form/README.en-US.md +2 -2
  42. package/dist/form/README.md +5 -3
  43. package/dist/form/form.vue +1 -1
  44. package/dist/form/type.ts +3 -3
  45. package/dist/form-item/form-item.css +6 -2
  46. package/dist/form-item/form-item.vue +30 -24
  47. package/dist/form-item/type.ts +2 -2
  48. package/dist/grid/README.md +3 -1
  49. package/dist/guide/README.md +3 -1
  50. package/dist/icon/README.md +10 -7
  51. package/dist/icon/icon.css +1633 -1624
  52. package/dist/image/README.md +3 -1
  53. package/dist/image-viewer/README.md +3 -1
  54. package/dist/index.js +13 -0
  55. package/dist/indexes/README.md +3 -1
  56. package/dist/indexes/computed.js +6 -2
  57. package/dist/indexes/indexes.css +7 -2
  58. package/dist/indexes/indexes.vue +1 -1
  59. package/dist/indexes/props.ts +5 -0
  60. package/dist/indexes/type.ts +5 -0
  61. package/dist/input/README.md +3 -1
  62. package/dist/input/input.vue +8 -32
  63. package/dist/link/README.md +3 -1
  64. package/dist/loading/README.md +3 -1
  65. package/dist/message/README.md +8 -3
  66. package/dist/mixins/page-scroll.d.ts +19 -0
  67. package/dist/mixins/skyline.js +1 -1
  68. package/dist/navbar/README.md +3 -1
  69. package/dist/notice-bar/README.md +3 -1
  70. package/dist/overlay/README.md +3 -1
  71. package/dist/picker/README.md +3 -1
  72. package/dist/popover/README.md +3 -1
  73. package/dist/popup/README.md +3 -1
  74. package/dist/progress/README.md +2 -2
  75. package/dist/pull-down-refresh/README.md +3 -1
  76. package/dist/qrcode/README.md +3 -1
  77. package/dist/radio/README.md +3 -1
  78. package/dist/rate/README.md +3 -1
  79. package/dist/rate/rate.css +1 -0
  80. package/dist/result/README.md +3 -1
  81. package/dist/search/README.md +3 -1
  82. package/dist/search/search.css +5 -0
  83. package/dist/search/search.vue +7 -12
  84. package/dist/segmented/README.en-US.md +42 -0
  85. package/dist/segmented/README.md +75 -0
  86. package/dist/segmented/props.ts +31 -0
  87. package/dist/segmented/segmented.css +66 -0
  88. package/dist/segmented/segmented.vue +174 -0
  89. package/dist/segmented/type.ts +41 -0
  90. package/dist/side-bar/README.md +3 -1
  91. package/dist/skeleton/README.md +3 -1
  92. package/dist/slider/README.md +3 -1
  93. package/dist/stepper/README.md +3 -1
  94. package/dist/steps/README.md +3 -1
  95. package/dist/sticky/README.md +3 -1
  96. package/dist/swipe-cell/README.md +3 -1
  97. package/dist/swiper/README.md +3 -1
  98. package/dist/switch/README.md +3 -1
  99. package/dist/tab-bar/README.md +3 -1
  100. package/dist/tab-bar-item/tab-bar-item.vue +4 -6
  101. package/dist/table/README.en-US.md +72 -0
  102. package/dist/table/README.md +117 -0
  103. package/dist/table/base-table-props.ts +105 -0
  104. package/dist/table/props.ts +94 -0
  105. package/dist/table/table.css +251 -0
  106. package/dist/table/table.vue +551 -0
  107. package/dist/table/type.ts +180 -0
  108. package/dist/tabs/README.md +3 -1
  109. package/dist/tabs/tabs.css +4 -0
  110. package/dist/tag/README.md +3 -1
  111. package/dist/textarea/README.md +3 -1
  112. package/dist/textarea/textarea.vue +1 -25
  113. package/dist/theme-light.css +282 -0
  114. package/dist/theme-light.css.d.ts +2 -0
  115. package/dist/theme-light.less +1 -0
  116. package/dist/theme-light.less.d.ts +2 -0
  117. package/dist/theme.css +467 -0
  118. package/dist/theme.css.d.ts +2 -0
  119. package/dist/theme.less +1 -0
  120. package/dist/theme.less.d.ts +2 -0
  121. package/dist/toast/README.md +3 -1
  122. package/dist/transition/README.md +4 -6
  123. package/dist/tree-select/README.md +3 -1
  124. package/dist/types/index.d.ts +17 -0
  125. package/dist/types/segmented.d.ts +7 -0
  126. package/dist/types/table.d.ts +7 -0
  127. package/dist/upload/README.md +3 -1
  128. package/dist/watermark/README.md +3 -1
  129. package/global.d.ts +2 -0
  130. package/package.json +66 -8
  131. package/script/postinstall.js +87 -0
  132. package/dist/common/src/superComponent.js +0 -5
  133. package/dist/common/validator.js +0 -210
  134. package/dist/script/postinstall.js +0 -46
@@ -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
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
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
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
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
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
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';
@@ -8,7 +8,9 @@ isComponent: true
8
8
 
9
9
  ## 引入
10
10
 
11
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
11
+ 推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-dialog />`。详细配置请参考 [快速开始](../getting-started)。
12
+
13
+ 如需手动引入:
12
14
 
13
15
  ```js
14
16
  import TDialog from '@tdesign/uniapp/dialog/dialog.vue';
@@ -8,7 +8,9 @@ isComponent: true
8
8
 
9
9
  ## 引入
10
10
 
11
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
11
+ 推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-divider />`。详细配置请参考 [快速开始](../getting-started)。
12
+
13
+ 如需手动引入:
12
14
 
13
15
  ```js
14
16
  import TDivider from '@tdesign/uniapp/divider/divider.vue';
@@ -9,7 +9,9 @@ isComponent: true
9
9
 
10
10
  ## 引入
11
11
 
12
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
12
+ 推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-drawer />`。详细配置请参考 [快速开始](../getting-started)。
13
+
14
+ 如需手动引入:
13
15
 
14
16
  ```js
15
17
  import TDrawer from '@tdesign/uniapp/drawer/drawer.vue';
@@ -272,6 +272,8 @@ export default {
272
272
 
273
273
  if (target) {
274
274
  this.computedLabel = target[labelAlias];
275
+ } else {
276
+ this.computedLabel = '';
275
277
  }
276
278
  },
277
279
  immediate: true,
@@ -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
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
14
+ 如需手动引入:
15
15
 
16
16
  ```js
17
17
  import DropdownMenu from '@tdesign/uniapp/dropdown-menu/dropdown-menu.vue';
@@ -148,7 +148,7 @@ export default {
148
148
  },
149
149
  getAllItems() {
150
150
  const menus = this.children?.map(data => ({
151
- label: data.label || data.computedLabel,
151
+ label: data.computedLabel || data.label,
152
152
  disabled: data.disabled,
153
153
  }));
154
154
 
@@ -8,7 +8,9 @@ isComponent: true
8
8
 
9
9
  ## 引入
10
10
 
11
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
11
+ 推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-empty />`。详细配置请参考 [快速开始](../getting-started)。
12
+
13
+ 如需手动引入:
12
14
 
13
15
  ```js
14
16
  import TEmpty from '@tdesign/uniapp/empty/empty.vue';
@@ -9,7 +9,9 @@ isComponent: true
9
9
 
10
10
  ## 引入
11
11
 
12
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
12
+ 推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-fab />`。详细配置请参考 [快速开始](../getting-started)。
13
+
14
+ 如需手动引入:
13
15
 
14
16
  ```js
15
17
  import TFab from '@tdesign/uniapp/fab/fab.vue';
package/dist/fab/fab.vue CHANGED
@@ -120,7 +120,7 @@ import { uniComponent } from '../common/src/index';
120
120
  import { prefix } from '../common/config';
121
121
  import props from './props';
122
122
  import useCustomNavbar from '../mixins/using-custom-navbar';
123
- import { unitConvert, getWindowInfo } from '../common/utils';
123
+ import { unitConvert, getWindowInfo, calcIcon } from '../common/utils';
124
124
  import tools from '../common/utils.wxs';
125
125
 
126
126
  const name = `${prefix}-fab`;
@@ -167,7 +167,7 @@ export default {
167
167
  ...baseButtonProps,
168
168
  shape: this.text ? 'round' : 'circle',
169
169
  ...this.buttonProps,
170
- icon: this.icon,
170
+ icon: calcIcon(this.icon),
171
171
  content: this.text,
172
172
  ariaLabel: this.ariaLabel,
173
173
  };
package/dist/fab/props.ts CHANGED
@@ -17,7 +17,7 @@ export default {
17
17
  },
18
18
  /** 图标 */
19
19
  icon: {
20
- type: String,
20
+ type: [String, Object],
21
21
  default: '',
22
22
  },
23
23
  /** 悬浮按钮的样式,常用于调整位置(即将废弃,建议使用 `style`) */
package/dist/fab/type.ts CHANGED
@@ -20,7 +20,7 @@ export interface TdFabProps {
20
20
  * 图标
21
21
  * @default ''
22
22
  */
23
- icon?: string;
23
+ icon?: string | Record<string, any>;
24
24
  /**
25
25
  * 悬浮按钮的样式,常用于调整位置(即将废弃,建议使用 `style`)
26
26
  * @default right: 16px; bottom: 32px;
@@ -8,7 +8,9 @@ isComponent: true
8
8
 
9
9
  ## 引入
10
10
 
11
- 可在 `main.ts` 或在需要使用的页面或组件中引入。
11
+ 推荐使用 easycom 模式引入组件,配置后无需手动 import 即可直接在模板中使用 `<t-footer />`。详细配置请参考 [快速开始](../getting-started)。
12
+
13
+ 如需手动引入:
12
14
 
13
15
  ```js
14
16
  import TFooter from '@tdesign/uniapp/footer/footer.vue';