@lytjs/plugin-validation 6.5.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/README.md +221 -0
- package/dist/index.cjs +274 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.mjs +248 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# @lytjs/plugin-validation
|
|
2
|
+
|
|
3
|
+
LytJS 官方表单验证插件,提供类型安全的表单验证能力。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lytjs/plugin-validation
|
|
9
|
+
# 或者
|
|
10
|
+
yarn add @lytjs/plugin-validation
|
|
11
|
+
# 或者
|
|
12
|
+
pnpm add @lytjs/plugin-validation
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 快速开始
|
|
16
|
+
|
|
17
|
+
### 作为插件使用
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createApp } from '@lytjs/core';
|
|
21
|
+
import pluginValidation from '@lytjs/plugin-validation';
|
|
22
|
+
|
|
23
|
+
const app = createApp();
|
|
24
|
+
app.use(pluginValidation, {
|
|
25
|
+
stopOnFirstError: false,
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 独立使用
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createValidationInstance } from '@lytjs/plugin-validation';
|
|
33
|
+
|
|
34
|
+
const validation = createValidationInstance();
|
|
35
|
+
|
|
36
|
+
// 验证单个字段
|
|
37
|
+
const result = await validation.validateField('email', 'test@example.com', [
|
|
38
|
+
{ type: 'required' },
|
|
39
|
+
{ type: 'email' },
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
if (result.valid) {
|
|
43
|
+
console.log('验证通过!');
|
|
44
|
+
} else {
|
|
45
|
+
console.log('验证失败:', result.errors);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 验证整个对象
|
|
49
|
+
const schema = {
|
|
50
|
+
username: {
|
|
51
|
+
rules: [{ type: 'required' }],
|
|
52
|
+
label: '用户名',
|
|
53
|
+
},
|
|
54
|
+
email: {
|
|
55
|
+
rules: [{ type: 'required' }, { type: 'email' }],
|
|
56
|
+
},
|
|
57
|
+
password: {
|
|
58
|
+
rules: [{ type: 'required' }, { type: 'minLength', value: 8 }],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const formData = {
|
|
63
|
+
username: 'testuser',
|
|
64
|
+
email: 'test@example.com',
|
|
65
|
+
password: 'password123',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const validationResult = await validation.validate(schema, formData);
|
|
69
|
+
console.log(validationResult.valid); // true 或 false
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 验证规则
|
|
73
|
+
|
|
74
|
+
### 内置规则
|
|
75
|
+
|
|
76
|
+
| 规则类型 | 说明 | 参数 |
|
|
77
|
+
| ----------- | -------------- | ------------------------- |
|
|
78
|
+
| `required` | 必填验证 | - |
|
|
79
|
+
| `email` | 邮箱格式验证 | - |
|
|
80
|
+
| `phone` | 手机号验证 | - |
|
|
81
|
+
| `number` | 数字验证 | - |
|
|
82
|
+
| `min` | 最小值验证 | `value: number` |
|
|
83
|
+
| `max` | 最大值验证 | `value: number` |
|
|
84
|
+
| `minLength` | 最小长度验证 | `value: number` |
|
|
85
|
+
| `maxLength` | 最大长度验证 | `value: number` |
|
|
86
|
+
| `length` | 精确长度验证 | `value: number` |
|
|
87
|
+
| `pattern` | 正则表达式验证 | `value: RegExp \| string` |
|
|
88
|
+
| `url` | URL 格式验证 | - |
|
|
89
|
+
| `uuid` | UUID 格式验证 | - |
|
|
90
|
+
| `date` | 日期格式验证 | - |
|
|
91
|
+
| `custom` | 自定义验证 | `validator: Function` |
|
|
92
|
+
|
|
93
|
+
### 规则示例
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const rules = [
|
|
97
|
+
// 必填
|
|
98
|
+
{ type: 'required' },
|
|
99
|
+
|
|
100
|
+
// 邮箱
|
|
101
|
+
{ type: 'email' },
|
|
102
|
+
|
|
103
|
+
// 手机号
|
|
104
|
+
{ type: 'phone' },
|
|
105
|
+
|
|
106
|
+
// 数字
|
|
107
|
+
{ type: 'number' },
|
|
108
|
+
|
|
109
|
+
// 范围
|
|
110
|
+
{ type: 'min', value: 18 },
|
|
111
|
+
{ type: 'max', value: 100 },
|
|
112
|
+
|
|
113
|
+
// 长度
|
|
114
|
+
{ type: 'minLength', value: 6 },
|
|
115
|
+
{ type: 'maxLength', value: 20 },
|
|
116
|
+
{ type: 'length', value: 4 },
|
|
117
|
+
|
|
118
|
+
// 正则
|
|
119
|
+
{ type: 'pattern', value: /^\d+$/ },
|
|
120
|
+
|
|
121
|
+
// URL
|
|
122
|
+
{ type: 'url' },
|
|
123
|
+
|
|
124
|
+
// UUID
|
|
125
|
+
{ type: 'uuid' },
|
|
126
|
+
|
|
127
|
+
// 日期
|
|
128
|
+
{ type: 'date' },
|
|
129
|
+
|
|
130
|
+
// 自定义
|
|
131
|
+
{
|
|
132
|
+
type: 'custom',
|
|
133
|
+
validator: (value: unknown, allValues?: Record<string, unknown>) => {
|
|
134
|
+
return String(value).includes('test');
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// 自定义错误消息
|
|
139
|
+
{ type: 'required', message: '请输入用户名' },
|
|
140
|
+
];
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## API
|
|
144
|
+
|
|
145
|
+
### ValidationInstance
|
|
146
|
+
|
|
147
|
+
#### `validate(schema: ValidationSchema, values: Record<string, unknown>): Promise<ValidationResult>`
|
|
148
|
+
|
|
149
|
+
验证整个对象。
|
|
150
|
+
|
|
151
|
+
#### `validateField(field: string, value: unknown, rules: ValidationRule[], allValues?: Record<string, unknown>): Promise<ValidationResult>`
|
|
152
|
+
|
|
153
|
+
验证单个字段。
|
|
154
|
+
|
|
155
|
+
#### `setMessages(messages: ValidationMessages): void`
|
|
156
|
+
|
|
157
|
+
设置自定义错误消息。
|
|
158
|
+
|
|
159
|
+
#### `addRule(type: ValidationRuleType, validator: Validator, defaultMessage?: string): void`
|
|
160
|
+
|
|
161
|
+
添加自定义验证规则。
|
|
162
|
+
|
|
163
|
+
### ValidationResult
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
interface ValidationResult {
|
|
167
|
+
valid: boolean;
|
|
168
|
+
errors: string[];
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 自定义消息
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const validation = createValidationInstance({
|
|
176
|
+
messages: {
|
|
177
|
+
required: '此字段为必填项',
|
|
178
|
+
email: '请输入有效的邮箱地址',
|
|
179
|
+
min: (value?: unknown, label?: string) => `${label || '值'}不能小于 ${value}`,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// 或者运行时设置
|
|
184
|
+
validation.setMessages({
|
|
185
|
+
required: '请填写这个字段',
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 自定义验证规则
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
validation.addRule(
|
|
193
|
+
'startsWithLyt',
|
|
194
|
+
(value: unknown) => String(value).startsWith('lyt'),
|
|
195
|
+
'必须以 lyt 开头',
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// 使用自定义规则
|
|
199
|
+
const result = await validation.validateField('name', 'lytjs', [{ type: 'startsWithLyt' as any }]);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 与 @lytjs/plugin-form 配合使用
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { createFormManager } from '@lytjs/plugin-form';
|
|
206
|
+
import { createValidationInstance } from '@lytjs/plugin-validation';
|
|
207
|
+
|
|
208
|
+
const validation = createValidationInstance();
|
|
209
|
+
|
|
210
|
+
const form = createFormManager({
|
|
211
|
+
fields: {
|
|
212
|
+
email: {
|
|
213
|
+
rules: [{ type: 'required' }, { type: 'email' }],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## 许可证
|
|
220
|
+
|
|
221
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createValidationInstance: () => createValidationInstance,
|
|
24
|
+
default: () => index_default,
|
|
25
|
+
defaultMessages: () => defaultMessages
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var import_core = require("@lytjs/core");
|
|
29
|
+
|
|
30
|
+
// src/rules/required.ts
|
|
31
|
+
var validateRequired = (value) => {
|
|
32
|
+
if (value == null) return false;
|
|
33
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
34
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
35
|
+
if (typeof value === "object" && value !== null) {
|
|
36
|
+
return Object.keys(value).length > 0;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/rules/email.ts
|
|
42
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
43
|
+
var validateEmail = (value) => {
|
|
44
|
+
if (value == null || value === "") return true;
|
|
45
|
+
return EMAIL_REGEX.test(String(value));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/rules/phone.ts
|
|
49
|
+
var PHONE_REGEX = /^1[3-9]\d{9}$/;
|
|
50
|
+
var validatePhone = (value) => {
|
|
51
|
+
if (value == null || value === "") return true;
|
|
52
|
+
return PHONE_REGEX.test(String(value));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/rules/number.ts
|
|
56
|
+
var validateNumber = (value) => {
|
|
57
|
+
if (value == null || value === "") return true;
|
|
58
|
+
return !isNaN(Number(value)) && isFinite(Number(value));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/rules/range.ts
|
|
62
|
+
var validateMin = (value, ruleValue) => {
|
|
63
|
+
if (value == null || value === "") return true;
|
|
64
|
+
const num = Number(value);
|
|
65
|
+
const min = Number(ruleValue);
|
|
66
|
+
if (isNaN(num) || isNaN(min)) return true;
|
|
67
|
+
return num >= min;
|
|
68
|
+
};
|
|
69
|
+
var validateMax = (value, ruleValue) => {
|
|
70
|
+
if (value == null || value === "") return true;
|
|
71
|
+
const num = Number(value);
|
|
72
|
+
const max = Number(ruleValue);
|
|
73
|
+
if (isNaN(num) || isNaN(max)) return true;
|
|
74
|
+
return num <= max;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/rules/string.ts
|
|
78
|
+
var validateMinLength = (value, ruleValue) => {
|
|
79
|
+
if (value == null || value === "") return true;
|
|
80
|
+
const str = String(value);
|
|
81
|
+
const min = Number(ruleValue);
|
|
82
|
+
if (isNaN(min)) return true;
|
|
83
|
+
return str.length >= min;
|
|
84
|
+
};
|
|
85
|
+
var validateMaxLength = (value, ruleValue) => {
|
|
86
|
+
if (value == null || value === "") return true;
|
|
87
|
+
const str = String(value);
|
|
88
|
+
const max = Number(ruleValue);
|
|
89
|
+
if (isNaN(max)) return true;
|
|
90
|
+
return str.length <= max;
|
|
91
|
+
};
|
|
92
|
+
var validateLength = (value, ruleValue) => {
|
|
93
|
+
if (value == null || value === "") return true;
|
|
94
|
+
const str = String(value);
|
|
95
|
+
const len = Number(ruleValue);
|
|
96
|
+
if (isNaN(len)) return true;
|
|
97
|
+
return str.length === len;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// src/rules/pattern.ts
|
|
101
|
+
var validatePattern = (value, ruleValue) => {
|
|
102
|
+
if (value == null || value === "") return true;
|
|
103
|
+
const regex = typeof ruleValue === "string" ? new RegExp(ruleValue) : ruleValue;
|
|
104
|
+
if (!(regex instanceof RegExp)) return true;
|
|
105
|
+
return regex.test(String(value));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/rules/url.ts
|
|
109
|
+
var URL_REGEX = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/i;
|
|
110
|
+
var validateUrl = (value) => {
|
|
111
|
+
if (value == null || value === "") return true;
|
|
112
|
+
return URL_REGEX.test(String(value));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/rules/uuid.ts
|
|
116
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
117
|
+
var validateUuid = (value) => {
|
|
118
|
+
if (value == null || value === "") return true;
|
|
119
|
+
return UUID_REGEX.test(String(value));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/rules/date.ts
|
|
123
|
+
var validateDate = (value) => {
|
|
124
|
+
if (value == null || value === "") return true;
|
|
125
|
+
const date = new Date(value);
|
|
126
|
+
return !isNaN(date.getTime());
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/rules/messages.ts
|
|
130
|
+
var defaultMessages = {
|
|
131
|
+
required: "\u6B64\u5B57\u6BB5\u4E3A\u5FC5\u586B\u9879",
|
|
132
|
+
email: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740",
|
|
133
|
+
phone: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u624B\u673A\u53F7\u7801",
|
|
134
|
+
number: "\u8BF7\u8F93\u5165\u6570\u5B57",
|
|
135
|
+
min: (value, label) => `${label || "\u503C"}\u4E0D\u80FD\u5C0F\u4E8E ${value}`,
|
|
136
|
+
max: (value, label) => `${label || "\u503C"}\u4E0D\u80FD\u5927\u4E8E ${value}`,
|
|
137
|
+
minLength: (value, label) => `${label || "\u957F\u5EA6"}\u4E0D\u80FD\u5C0F\u4E8E ${value}`,
|
|
138
|
+
maxLength: (value, label) => `${label || "\u957F\u5EA6"}\u4E0D\u80FD\u5927\u4E8E ${value}`,
|
|
139
|
+
length: (value, label) => `${label || "\u957F\u5EA6"}\u5FC5\u987B\u7B49\u4E8E ${value}`,
|
|
140
|
+
pattern: "\u683C\u5F0F\u4E0D\u6B63\u786E",
|
|
141
|
+
url: "\u8BF7\u8F93\u5165\u6709\u6548\u7684URL",
|
|
142
|
+
uuid: "\u8BF7\u8F93\u5165\u6709\u6548\u7684UUID",
|
|
143
|
+
date: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u65E5\u671F",
|
|
144
|
+
custom: "\u6821\u9A8C\u5931\u8D25"
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/index.ts
|
|
148
|
+
var builtInValidators = {
|
|
149
|
+
required: validateRequired,
|
|
150
|
+
email: validateEmail,
|
|
151
|
+
phone: validatePhone,
|
|
152
|
+
number: validateNumber,
|
|
153
|
+
min: validateMin,
|
|
154
|
+
max: validateMax,
|
|
155
|
+
minLength: validateMinLength,
|
|
156
|
+
maxLength: validateMaxLength,
|
|
157
|
+
length: validateLength,
|
|
158
|
+
pattern: validatePattern,
|
|
159
|
+
url: validateUrl,
|
|
160
|
+
uuid: validateUuid,
|
|
161
|
+
date: validateDate,
|
|
162
|
+
custom: () => true
|
|
163
|
+
};
|
|
164
|
+
function getMessage(messages, type, ruleValue, label) {
|
|
165
|
+
const msg = messages[type];
|
|
166
|
+
if (typeof msg === "function") {
|
|
167
|
+
return msg(ruleValue, label);
|
|
168
|
+
}
|
|
169
|
+
return msg || "\u6821\u9A8C\u5931\u8D25";
|
|
170
|
+
}
|
|
171
|
+
async function validateRule(rule, value, allValues, validators, messages, label) {
|
|
172
|
+
let isValid;
|
|
173
|
+
if (rule.type === "custom" && rule.validator) {
|
|
174
|
+
isValid = await rule.validator(value, allValues);
|
|
175
|
+
} else {
|
|
176
|
+
const validator = validators[rule.type];
|
|
177
|
+
if (!validator) return null;
|
|
178
|
+
isValid = await validator(value, rule.value, allValues);
|
|
179
|
+
}
|
|
180
|
+
if (!isValid) {
|
|
181
|
+
return rule.message || getMessage(messages, rule.type, rule.value, label);
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
async function validateFieldInternal(value, config, allValues, validators, messages, stopOnFirstError = false) {
|
|
186
|
+
const errors = [];
|
|
187
|
+
for (const rule of config.rules) {
|
|
188
|
+
const error = await validateRule(rule, value, allValues, validators, messages, config.label);
|
|
189
|
+
if (error) {
|
|
190
|
+
errors.push(error);
|
|
191
|
+
if (stopOnFirstError) break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
valid: errors.length === 0,
|
|
196
|
+
errors
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function createValidationInstance(options) {
|
|
200
|
+
const { messages = {}, stopOnFirstError = false } = options || {};
|
|
201
|
+
const customMessages = { ...defaultMessages, ...messages };
|
|
202
|
+
const customValidators = {};
|
|
203
|
+
const instance = {
|
|
204
|
+
validateField: async (field, value, rules, allValues) => {
|
|
205
|
+
return await validateFieldInternal(
|
|
206
|
+
value,
|
|
207
|
+
{ rules },
|
|
208
|
+
allValues || {},
|
|
209
|
+
{ ...builtInValidators, ...customValidators },
|
|
210
|
+
customMessages,
|
|
211
|
+
stopOnFirstError
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
validate: async (schema, values) => {
|
|
215
|
+
const allErrors = [];
|
|
216
|
+
for (const [field, config] of Object.entries(schema)) {
|
|
217
|
+
const result = await validateFieldInternal(
|
|
218
|
+
values[field],
|
|
219
|
+
config,
|
|
220
|
+
values,
|
|
221
|
+
{ ...builtInValidators, ...customValidators },
|
|
222
|
+
customMessages,
|
|
223
|
+
stopOnFirstError
|
|
224
|
+
);
|
|
225
|
+
if (!result.valid) {
|
|
226
|
+
allErrors.push(...result.errors);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
valid: allErrors.length === 0,
|
|
231
|
+
errors: allErrors
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
setMessages: (newMessages) => {
|
|
235
|
+
Object.assign(customMessages, newMessages);
|
|
236
|
+
},
|
|
237
|
+
addRule: (type, validator, defaultMessage) => {
|
|
238
|
+
customValidators[type] = validator;
|
|
239
|
+
if (defaultMessage) {
|
|
240
|
+
customMessages[type] = defaultMessage;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
return instance;
|
|
245
|
+
}
|
|
246
|
+
var pluginValidation = (0, import_core.definePlugin)({
|
|
247
|
+
name: "validation",
|
|
248
|
+
version: "6.0.0",
|
|
249
|
+
description: "LytJS official validation plugin for type-safe form validation",
|
|
250
|
+
author: "LytJS Team",
|
|
251
|
+
keywords: ["lytjs", "validation", "form", "validator"],
|
|
252
|
+
schema: {
|
|
253
|
+
type: "object",
|
|
254
|
+
object: {
|
|
255
|
+
properties: {
|
|
256
|
+
messages: { type: "object", default: {} },
|
|
257
|
+
validateOnChange: { type: "boolean", default: false },
|
|
258
|
+
validateOnBlur: { type: "boolean", default: false },
|
|
259
|
+
stopOnFirstError: { type: "boolean", default: false }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
install(app, options) {
|
|
264
|
+
const validation = createValidationInstance(options);
|
|
265
|
+
app.config.globalProperties.$validation = validation;
|
|
266
|
+
app.provide("lyt-validation", validation);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
var index_default = pluginValidation;
|
|
270
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
271
|
+
0 && (module.exports = {
|
|
272
|
+
createValidationInstance,
|
|
273
|
+
defaultMessages
|
|
274
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as _lytjs_core from '@lytjs/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @lytjs/plugin-validation - 类型定义
|
|
5
|
+
*/
|
|
6
|
+
type ValidationRuleType = 'required' | 'email' | 'phone' | 'number' | 'min' | 'max' | 'minLength' | 'maxLength' | 'length' | 'pattern' | 'url' | 'uuid' | 'date' | 'custom';
|
|
7
|
+
interface ValidationRule {
|
|
8
|
+
type: ValidationRuleType;
|
|
9
|
+
message?: string;
|
|
10
|
+
value?: unknown;
|
|
11
|
+
validator?: (value: unknown, allValues?: Record<string, unknown>) => boolean | Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
interface Validator {
|
|
14
|
+
(value: unknown, ruleValue?: unknown, allValues?: Record<string, unknown>): boolean | Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
interface ValidationResult {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
errors: string[];
|
|
19
|
+
}
|
|
20
|
+
interface FieldValidationConfig {
|
|
21
|
+
rules: ValidationRule[];
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
interface ValidationSchema {
|
|
25
|
+
[field: string]: FieldValidationConfig;
|
|
26
|
+
}
|
|
27
|
+
interface ValidationMessages {
|
|
28
|
+
[key: string]: string | ((value?: unknown, label?: string) => string);
|
|
29
|
+
}
|
|
30
|
+
interface ValidationOptions {
|
|
31
|
+
messages?: ValidationMessages;
|
|
32
|
+
validateOnChange?: boolean;
|
|
33
|
+
validateOnBlur?: boolean;
|
|
34
|
+
stopOnFirstError?: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface ValidationInstance {
|
|
37
|
+
validate: (schema: ValidationSchema, values: Record<string, unknown>) => Promise<ValidationResult>;
|
|
38
|
+
validateField: (field: string, value: unknown, rules: ValidationRule[], allValues?: Record<string, unknown>) => Promise<ValidationResult>;
|
|
39
|
+
setMessages: (messages: ValidationMessages) => void;
|
|
40
|
+
addRule: (type: ValidationRuleType, validator: Validator, defaultMessage?: string) => void;
|
|
41
|
+
}
|
|
42
|
+
interface ValidationPluginOptions extends ValidationOptions {
|
|
43
|
+
name?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare const defaultMessages: ValidationMessages;
|
|
47
|
+
|
|
48
|
+
declare function createValidationInstance(options?: ValidationPluginOptions): ValidationInstance;
|
|
49
|
+
declare const pluginValidation: _lytjs_core.PluginDefinition<unknown>;
|
|
50
|
+
|
|
51
|
+
export { type FieldValidationConfig, type ValidationInstance, type ValidationMessages, type ValidationOptions, type ValidationPluginOptions, type ValidationResult, type ValidationRule, type ValidationRuleType, type ValidationSchema, type Validator, createValidationInstance, pluginValidation as default, defaultMessages };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as _lytjs_core from '@lytjs/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @lytjs/plugin-validation - 类型定义
|
|
5
|
+
*/
|
|
6
|
+
type ValidationRuleType = 'required' | 'email' | 'phone' | 'number' | 'min' | 'max' | 'minLength' | 'maxLength' | 'length' | 'pattern' | 'url' | 'uuid' | 'date' | 'custom';
|
|
7
|
+
interface ValidationRule {
|
|
8
|
+
type: ValidationRuleType;
|
|
9
|
+
message?: string;
|
|
10
|
+
value?: unknown;
|
|
11
|
+
validator?: (value: unknown, allValues?: Record<string, unknown>) => boolean | Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
interface Validator {
|
|
14
|
+
(value: unknown, ruleValue?: unknown, allValues?: Record<string, unknown>): boolean | Promise<boolean>;
|
|
15
|
+
}
|
|
16
|
+
interface ValidationResult {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
errors: string[];
|
|
19
|
+
}
|
|
20
|
+
interface FieldValidationConfig {
|
|
21
|
+
rules: ValidationRule[];
|
|
22
|
+
label?: string;
|
|
23
|
+
}
|
|
24
|
+
interface ValidationSchema {
|
|
25
|
+
[field: string]: FieldValidationConfig;
|
|
26
|
+
}
|
|
27
|
+
interface ValidationMessages {
|
|
28
|
+
[key: string]: string | ((value?: unknown, label?: string) => string);
|
|
29
|
+
}
|
|
30
|
+
interface ValidationOptions {
|
|
31
|
+
messages?: ValidationMessages;
|
|
32
|
+
validateOnChange?: boolean;
|
|
33
|
+
validateOnBlur?: boolean;
|
|
34
|
+
stopOnFirstError?: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface ValidationInstance {
|
|
37
|
+
validate: (schema: ValidationSchema, values: Record<string, unknown>) => Promise<ValidationResult>;
|
|
38
|
+
validateField: (field: string, value: unknown, rules: ValidationRule[], allValues?: Record<string, unknown>) => Promise<ValidationResult>;
|
|
39
|
+
setMessages: (messages: ValidationMessages) => void;
|
|
40
|
+
addRule: (type: ValidationRuleType, validator: Validator, defaultMessage?: string) => void;
|
|
41
|
+
}
|
|
42
|
+
interface ValidationPluginOptions extends ValidationOptions {
|
|
43
|
+
name?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare const defaultMessages: ValidationMessages;
|
|
47
|
+
|
|
48
|
+
declare function createValidationInstance(options?: ValidationPluginOptions): ValidationInstance;
|
|
49
|
+
declare const pluginValidation: _lytjs_core.PluginDefinition<unknown>;
|
|
50
|
+
|
|
51
|
+
export { type FieldValidationConfig, type ValidationInstance, type ValidationMessages, type ValidationOptions, type ValidationPluginOptions, type ValidationResult, type ValidationRule, type ValidationRuleType, type ValidationSchema, type Validator, createValidationInstance, pluginValidation as default, defaultMessages };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { definePlugin } from "@lytjs/core";
|
|
3
|
+
|
|
4
|
+
// src/rules/required.ts
|
|
5
|
+
var validateRequired = (value) => {
|
|
6
|
+
if (value == null) return false;
|
|
7
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
8
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
9
|
+
if (typeof value === "object" && value !== null) {
|
|
10
|
+
return Object.keys(value).length > 0;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/rules/email.ts
|
|
16
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17
|
+
var validateEmail = (value) => {
|
|
18
|
+
if (value == null || value === "") return true;
|
|
19
|
+
return EMAIL_REGEX.test(String(value));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/rules/phone.ts
|
|
23
|
+
var PHONE_REGEX = /^1[3-9]\d{9}$/;
|
|
24
|
+
var validatePhone = (value) => {
|
|
25
|
+
if (value == null || value === "") return true;
|
|
26
|
+
return PHONE_REGEX.test(String(value));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/rules/number.ts
|
|
30
|
+
var validateNumber = (value) => {
|
|
31
|
+
if (value == null || value === "") return true;
|
|
32
|
+
return !isNaN(Number(value)) && isFinite(Number(value));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/rules/range.ts
|
|
36
|
+
var validateMin = (value, ruleValue) => {
|
|
37
|
+
if (value == null || value === "") return true;
|
|
38
|
+
const num = Number(value);
|
|
39
|
+
const min = Number(ruleValue);
|
|
40
|
+
if (isNaN(num) || isNaN(min)) return true;
|
|
41
|
+
return num >= min;
|
|
42
|
+
};
|
|
43
|
+
var validateMax = (value, ruleValue) => {
|
|
44
|
+
if (value == null || value === "") return true;
|
|
45
|
+
const num = Number(value);
|
|
46
|
+
const max = Number(ruleValue);
|
|
47
|
+
if (isNaN(num) || isNaN(max)) return true;
|
|
48
|
+
return num <= max;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/rules/string.ts
|
|
52
|
+
var validateMinLength = (value, ruleValue) => {
|
|
53
|
+
if (value == null || value === "") return true;
|
|
54
|
+
const str = String(value);
|
|
55
|
+
const min = Number(ruleValue);
|
|
56
|
+
if (isNaN(min)) return true;
|
|
57
|
+
return str.length >= min;
|
|
58
|
+
};
|
|
59
|
+
var validateMaxLength = (value, ruleValue) => {
|
|
60
|
+
if (value == null || value === "") return true;
|
|
61
|
+
const str = String(value);
|
|
62
|
+
const max = Number(ruleValue);
|
|
63
|
+
if (isNaN(max)) return true;
|
|
64
|
+
return str.length <= max;
|
|
65
|
+
};
|
|
66
|
+
var validateLength = (value, ruleValue) => {
|
|
67
|
+
if (value == null || value === "") return true;
|
|
68
|
+
const str = String(value);
|
|
69
|
+
const len = Number(ruleValue);
|
|
70
|
+
if (isNaN(len)) return true;
|
|
71
|
+
return str.length === len;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/rules/pattern.ts
|
|
75
|
+
var validatePattern = (value, ruleValue) => {
|
|
76
|
+
if (value == null || value === "") return true;
|
|
77
|
+
const regex = typeof ruleValue === "string" ? new RegExp(ruleValue) : ruleValue;
|
|
78
|
+
if (!(regex instanceof RegExp)) return true;
|
|
79
|
+
return regex.test(String(value));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/rules/url.ts
|
|
83
|
+
var URL_REGEX = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/i;
|
|
84
|
+
var validateUrl = (value) => {
|
|
85
|
+
if (value == null || value === "") return true;
|
|
86
|
+
return URL_REGEX.test(String(value));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/rules/uuid.ts
|
|
90
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
91
|
+
var validateUuid = (value) => {
|
|
92
|
+
if (value == null || value === "") return true;
|
|
93
|
+
return UUID_REGEX.test(String(value));
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/rules/date.ts
|
|
97
|
+
var validateDate = (value) => {
|
|
98
|
+
if (value == null || value === "") return true;
|
|
99
|
+
const date = new Date(value);
|
|
100
|
+
return !isNaN(date.getTime());
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/rules/messages.ts
|
|
104
|
+
var defaultMessages = {
|
|
105
|
+
required: "\u6B64\u5B57\u6BB5\u4E3A\u5FC5\u586B\u9879",
|
|
106
|
+
email: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u90AE\u7BB1\u5730\u5740",
|
|
107
|
+
phone: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u624B\u673A\u53F7\u7801",
|
|
108
|
+
number: "\u8BF7\u8F93\u5165\u6570\u5B57",
|
|
109
|
+
min: (value, label) => `${label || "\u503C"}\u4E0D\u80FD\u5C0F\u4E8E ${value}`,
|
|
110
|
+
max: (value, label) => `${label || "\u503C"}\u4E0D\u80FD\u5927\u4E8E ${value}`,
|
|
111
|
+
minLength: (value, label) => `${label || "\u957F\u5EA6"}\u4E0D\u80FD\u5C0F\u4E8E ${value}`,
|
|
112
|
+
maxLength: (value, label) => `${label || "\u957F\u5EA6"}\u4E0D\u80FD\u5927\u4E8E ${value}`,
|
|
113
|
+
length: (value, label) => `${label || "\u957F\u5EA6"}\u5FC5\u987B\u7B49\u4E8E ${value}`,
|
|
114
|
+
pattern: "\u683C\u5F0F\u4E0D\u6B63\u786E",
|
|
115
|
+
url: "\u8BF7\u8F93\u5165\u6709\u6548\u7684URL",
|
|
116
|
+
uuid: "\u8BF7\u8F93\u5165\u6709\u6548\u7684UUID",
|
|
117
|
+
date: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u65E5\u671F",
|
|
118
|
+
custom: "\u6821\u9A8C\u5931\u8D25"
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/index.ts
|
|
122
|
+
var builtInValidators = {
|
|
123
|
+
required: validateRequired,
|
|
124
|
+
email: validateEmail,
|
|
125
|
+
phone: validatePhone,
|
|
126
|
+
number: validateNumber,
|
|
127
|
+
min: validateMin,
|
|
128
|
+
max: validateMax,
|
|
129
|
+
minLength: validateMinLength,
|
|
130
|
+
maxLength: validateMaxLength,
|
|
131
|
+
length: validateLength,
|
|
132
|
+
pattern: validatePattern,
|
|
133
|
+
url: validateUrl,
|
|
134
|
+
uuid: validateUuid,
|
|
135
|
+
date: validateDate,
|
|
136
|
+
custom: () => true
|
|
137
|
+
};
|
|
138
|
+
function getMessage(messages, type, ruleValue, label) {
|
|
139
|
+
const msg = messages[type];
|
|
140
|
+
if (typeof msg === "function") {
|
|
141
|
+
return msg(ruleValue, label);
|
|
142
|
+
}
|
|
143
|
+
return msg || "\u6821\u9A8C\u5931\u8D25";
|
|
144
|
+
}
|
|
145
|
+
async function validateRule(rule, value, allValues, validators, messages, label) {
|
|
146
|
+
let isValid;
|
|
147
|
+
if (rule.type === "custom" && rule.validator) {
|
|
148
|
+
isValid = await rule.validator(value, allValues);
|
|
149
|
+
} else {
|
|
150
|
+
const validator = validators[rule.type];
|
|
151
|
+
if (!validator) return null;
|
|
152
|
+
isValid = await validator(value, rule.value, allValues);
|
|
153
|
+
}
|
|
154
|
+
if (!isValid) {
|
|
155
|
+
return rule.message || getMessage(messages, rule.type, rule.value, label);
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
async function validateFieldInternal(value, config, allValues, validators, messages, stopOnFirstError = false) {
|
|
160
|
+
const errors = [];
|
|
161
|
+
for (const rule of config.rules) {
|
|
162
|
+
const error = await validateRule(rule, value, allValues, validators, messages, config.label);
|
|
163
|
+
if (error) {
|
|
164
|
+
errors.push(error);
|
|
165
|
+
if (stopOnFirstError) break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
valid: errors.length === 0,
|
|
170
|
+
errors
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function createValidationInstance(options) {
|
|
174
|
+
const { messages = {}, stopOnFirstError = false } = options || {};
|
|
175
|
+
const customMessages = { ...defaultMessages, ...messages };
|
|
176
|
+
const customValidators = {};
|
|
177
|
+
const instance = {
|
|
178
|
+
validateField: async (field, value, rules, allValues) => {
|
|
179
|
+
return await validateFieldInternal(
|
|
180
|
+
value,
|
|
181
|
+
{ rules },
|
|
182
|
+
allValues || {},
|
|
183
|
+
{ ...builtInValidators, ...customValidators },
|
|
184
|
+
customMessages,
|
|
185
|
+
stopOnFirstError
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
validate: async (schema, values) => {
|
|
189
|
+
const allErrors = [];
|
|
190
|
+
for (const [field, config] of Object.entries(schema)) {
|
|
191
|
+
const result = await validateFieldInternal(
|
|
192
|
+
values[field],
|
|
193
|
+
config,
|
|
194
|
+
values,
|
|
195
|
+
{ ...builtInValidators, ...customValidators },
|
|
196
|
+
customMessages,
|
|
197
|
+
stopOnFirstError
|
|
198
|
+
);
|
|
199
|
+
if (!result.valid) {
|
|
200
|
+
allErrors.push(...result.errors);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
valid: allErrors.length === 0,
|
|
205
|
+
errors: allErrors
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
setMessages: (newMessages) => {
|
|
209
|
+
Object.assign(customMessages, newMessages);
|
|
210
|
+
},
|
|
211
|
+
addRule: (type, validator, defaultMessage) => {
|
|
212
|
+
customValidators[type] = validator;
|
|
213
|
+
if (defaultMessage) {
|
|
214
|
+
customMessages[type] = defaultMessage;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
return instance;
|
|
219
|
+
}
|
|
220
|
+
var pluginValidation = definePlugin({
|
|
221
|
+
name: "validation",
|
|
222
|
+
version: "6.0.0",
|
|
223
|
+
description: "LytJS official validation plugin for type-safe form validation",
|
|
224
|
+
author: "LytJS Team",
|
|
225
|
+
keywords: ["lytjs", "validation", "form", "validator"],
|
|
226
|
+
schema: {
|
|
227
|
+
type: "object",
|
|
228
|
+
object: {
|
|
229
|
+
properties: {
|
|
230
|
+
messages: { type: "object", default: {} },
|
|
231
|
+
validateOnChange: { type: "boolean", default: false },
|
|
232
|
+
validateOnBlur: { type: "boolean", default: false },
|
|
233
|
+
stopOnFirstError: { type: "boolean", default: false }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
install(app, options) {
|
|
238
|
+
const validation = createValidationInstance(options);
|
|
239
|
+
app.config.globalProperties.$validation = validation;
|
|
240
|
+
app.provide("lyt-validation", validation);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
var index_default = pluginValidation;
|
|
244
|
+
export {
|
|
245
|
+
createValidationInstance,
|
|
246
|
+
index_default as default,
|
|
247
|
+
defaultMessages
|
|
248
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lytjs/plugin-validation",
|
|
3
|
+
"version": "6.5.0",
|
|
4
|
+
"description": "LytJS official validation plugin for type-safe form validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:coverage": "vitest run --coverage",
|
|
27
|
+
"type-check": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
29
|
+
"clean": "rm -rf dist"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@lytjs/core": "^6.0.0",
|
|
33
|
+
"@lytjs/reactivity": "^6.0.0",
|
|
34
|
+
"@lytjs/plugin-form": "^6.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.4.0",
|
|
39
|
+
"vitest": "^3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://gitee.com/lytjs/lytjs.git",
|
|
45
|
+
"directory": "packages/plugins/packages/plugin-validation"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"lytjs",
|
|
49
|
+
"validation",
|
|
50
|
+
"form",
|
|
51
|
+
"validator"
|
|
52
|
+
]
|
|
53
|
+
}
|