@truenewx/tnxvue3 2.6.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 +3 -0
- package/package.json +76 -0
- package/sample/App.vue +19 -0
- package/sample/main.js +11 -0
- package/sample/pages/index.vue +79 -0
- package/sample/pages/info.vue +28 -0
- package/sample/tnx.js +31 -0
- package/src/aj-captcha/Verify/VerifyPoints.vue +258 -0
- package/src/aj-captcha/Verify/VerifySlide.vue +379 -0
- package/src/aj-captcha/Verify.vue +375 -0
- package/src/aj-captcha/api/index.js +19 -0
- package/src/aj-captcha/utils/ase.js +11 -0
- package/src/aj-captcha/utils/util.js +35 -0
- package/src/ant-design/tnxad-theme.css +5 -0
- package/src/ant-design/tnxad.css +8 -0
- package/src/ant-design/tnxad.js +23 -0
- package/src/element-plus/alert/Alert.vue +112 -0
- package/src/element-plus/avatar/Avatar.vue +124 -0
- package/src/element-plus/button/Button.vue +184 -0
- package/src/element-plus/check-icon/CheckIcon.vue +61 -0
- package/src/element-plus/close-error-button/CloseErrorButton.vue +45 -0
- package/src/element-plus/curd/Curd.vue +224 -0
- package/src/element-plus/date-picker/DatePicker.vue +206 -0
- package/src/element-plus/date-range/DateRange.vue +78 -0
- package/src/element-plus/datetime-picker/DateTimePicker.vue +129 -0
- package/src/element-plus/detail-form/DetailForm.vue +88 -0
- package/src/element-plus/dialog/Dialog.vue +259 -0
- package/src/element-plus/dialog/DialogContent.vue +13 -0
- package/src/element-plus/drawer/Drawer.vue +175 -0
- package/src/element-plus/dropdown-item/DropdownItem.vue +30 -0
- package/src/element-plus/enum-select/EnumSelect.vue +125 -0
- package/src/element-plus/fetch-cascader/FetchCascader.vue +138 -0
- package/src/element-plus/fetch-select/FetchSelect.vue +166 -0
- package/src/element-plus/fetch-tags/FetchTags.vue +122 -0
- package/src/element-plus/fss-upload/FssUpload.vue +306 -0
- package/src/element-plus/fss-view/FssView.vue +163 -0
- package/src/element-plus/icon/Icon.vue +221 -0
- package/src/element-plus/input-number/InputNumber.vue +150 -0
- package/src/element-plus/paged/Paged.vue +76 -0
- package/src/element-plus/permission-tree/PermissionTree.vue +184 -0
- package/src/element-plus/query-form/QueryForm.vue +138 -0
- package/src/element-plus/query-table/QueryTable.vue +402 -0
- package/src/element-plus/region-cascader/RegionCascader.vue +108 -0
- package/src/element-plus/select/Select.vue +446 -0
- package/src/element-plus/slider/Slider.vue +88 -0
- package/src/element-plus/steps-nav/StepsNav.vue +57 -0
- package/src/element-plus/submit-form/SubmitForm.vue +236 -0
- package/src/element-plus/table-column/TableColumn.vue +32 -0
- package/src/element-plus/tabs/Tabs.vue +93 -0
- package/src/element-plus/tnxel.css +890 -0
- package/src/element-plus/tnxel.js +528 -0
- package/src/element-plus/transfer/Transfer.vue +117 -0
- package/src/element-plus/upload/Upload.vue +856 -0
- package/src/percent/Percent.vue +12 -0
- package/src/text/Text.vue +33 -0
- package/src/tnxvue-cli.js +64 -0
- package/src/tnxvue-router.js +161 -0
- package/src/tnxvue-validator.js +365 -0
- package/src/tnxvue.css +12 -0
- package/src/tnxvue.js +343 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span v-if="text">{{ prepend }}{{ text }}{{ append }}</span>
|
|
3
|
+
<span v-else-if="emptyText">{{ emptyText }}</span>
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
export default {
|
|
8
|
+
name: 'TnxvueText',
|
|
9
|
+
props: {
|
|
10
|
+
value: [String, Number, Boolean, Function],
|
|
11
|
+
prepend: String,
|
|
12
|
+
append: String,
|
|
13
|
+
emptyText: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: '-',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
computed: {
|
|
19
|
+
text() {
|
|
20
|
+
if (this.value !== undefined && this.value !== null) {
|
|
21
|
+
if (typeof this.value === 'function') {
|
|
22
|
+
return this.value();
|
|
23
|
+
}
|
|
24
|
+
if (typeof this.value === 'boolean') {
|
|
25
|
+
return this.value.toText();
|
|
26
|
+
}
|
|
27
|
+
return this.value;
|
|
28
|
+
}
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// tnxvue-cli.js
|
|
2
|
+
const TerserPlugin = require('terser-webpack-plugin');
|
|
3
|
+
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
uglify(config, options) {
|
|
7
|
+
Object.assign(config, {
|
|
8
|
+
optimization: {
|
|
9
|
+
minimizer: [new TerserPlugin({
|
|
10
|
+
terserOptions: Object.assign({
|
|
11
|
+
compress: {
|
|
12
|
+
warnings: false,
|
|
13
|
+
drop_console: false,
|
|
14
|
+
drop_debugger: true,
|
|
15
|
+
}
|
|
16
|
+
}, options)
|
|
17
|
+
})]
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
copy(config, dependencies, libs, patterns) {
|
|
22
|
+
let pluginPatterns = [];
|
|
23
|
+
for (let lib of libs) {
|
|
24
|
+
let from = lib.path;
|
|
25
|
+
let to = 'libs/js/' + lib.name;
|
|
26
|
+
if (config.mode === 'production') {
|
|
27
|
+
let version = dependencies[lib.name];
|
|
28
|
+
if (version) {
|
|
29
|
+
to += '-' + version;
|
|
30
|
+
}
|
|
31
|
+
from += lib.prod;
|
|
32
|
+
to += lib.prod;
|
|
33
|
+
}
|
|
34
|
+
if (!from.endsWith('/')) {
|
|
35
|
+
from += '.js';
|
|
36
|
+
}
|
|
37
|
+
if (!to.endsWith('/')) {
|
|
38
|
+
to += '.js';
|
|
39
|
+
}
|
|
40
|
+
pluginPatterns.push({
|
|
41
|
+
from: './node_modules/' + from,
|
|
42
|
+
to: './' + to,
|
|
43
|
+
});
|
|
44
|
+
let globalVarName = config.externals[lib.name];
|
|
45
|
+
if (globalVarName) {
|
|
46
|
+
let baseUrl = process.env.VUE_APP_VIEW_BASE_URL;
|
|
47
|
+
if (!baseUrl.endsWith('/')) {
|
|
48
|
+
baseUrl += '/';
|
|
49
|
+
}
|
|
50
|
+
process.env['VUE_APP_LIBS_' + globalVarName] = baseUrl + to;
|
|
51
|
+
}
|
|
52
|
+
if (lib.map) {
|
|
53
|
+
pluginPatterns.push({
|
|
54
|
+
from: './node_modules/' + lib.path + lib.map,
|
|
55
|
+
to: './libs/js/',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (patterns?.length) {
|
|
60
|
+
pluginPatterns = pluginPatterns.concat(patterns);
|
|
61
|
+
}
|
|
62
|
+
config.plugins.push(new CopyWebpackPlugin({patterns: pluginPatterns}));
|
|
63
|
+
},
|
|
64
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基于Vue的路由器构建函数
|
|
3
|
+
*/
|
|
4
|
+
import {FunctionUtil, NetUtil} from '@truenewx/tnxcore/src/tnxcore-util';
|
|
5
|
+
|
|
6
|
+
function addRoute(routes, superiorPath, item, fnImportPage) {
|
|
7
|
+
if (item && item.path) {
|
|
8
|
+
let page = item.page || item.path.replace(/\/:[a-zA-Z0-9_]+/g, '');
|
|
9
|
+
let route = {
|
|
10
|
+
path: item.path,
|
|
11
|
+
meta: {
|
|
12
|
+
superiorPath: superiorPath,
|
|
13
|
+
page: page,
|
|
14
|
+
cache: {}, // 路由级缓存
|
|
15
|
+
isHistory() { // 通过setTimeout()方式调用才能确保获得正确结果
|
|
16
|
+
return this.historyFrom !== undefined;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
component() {
|
|
20
|
+
return fnImportPage(page);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
// 如果直接定义route的redirect/alias字段,则item的redirect/alias为undefined时,route仍然有redirect/alias字段,只是其值为undefined,这将导致VueRouter报错
|
|
24
|
+
if (item.redirect) {
|
|
25
|
+
route.redirect = item.redirect;
|
|
26
|
+
}
|
|
27
|
+
if (item.alias) {
|
|
28
|
+
route.alias = item.alias;
|
|
29
|
+
}
|
|
30
|
+
routes.push(route);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function applyItemsToRoutes(superiorPath, items, routes, fnImportPage) {
|
|
35
|
+
if (items && items.length) {
|
|
36
|
+
items.forEach(item => {
|
|
37
|
+
addRoute(routes, superiorPath, item, fnImportPage);
|
|
38
|
+
applyItemsToRoutes(item.path, item.subs, routes, fnImportPage);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function instantiatePath(path, params) {
|
|
44
|
+
if (path && path.contains('/:')) {
|
|
45
|
+
if (params) {
|
|
46
|
+
Object.keys(params).forEach(key => {
|
|
47
|
+
path = path.replace('/:' + key + '/', '/' + params[key] + '/');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (path.contains('/:')) { // 参数替换完之后,还有路径参数,则为无效路径,返回首页
|
|
51
|
+
console.warn('路径中的参数无法获得参数值,请确保具有参数的路径所属菜单项的下级菜单路径包含相同的参数:' + path);
|
|
52
|
+
return '/';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return path;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getCurrentRoute(router) {
|
|
59
|
+
return router.currentRoute._value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default function (VueRouter, menu, fnImportPage) {
|
|
63
|
+
let items;
|
|
64
|
+
if (Array.isArray(menu)) {
|
|
65
|
+
items = [];
|
|
66
|
+
menu.forEach(function (m) {
|
|
67
|
+
items = items.concat(m.items);
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
items = menu.items;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const routes = [];
|
|
74
|
+
applyItemsToRoutes(undefined, items, routes, fnImportPage);
|
|
75
|
+
|
|
76
|
+
const routerHistory = VueRouter.createHistory();
|
|
77
|
+
const router = VueRouter.createRouter({
|
|
78
|
+
history: routerHistory,
|
|
79
|
+
routes,
|
|
80
|
+
});
|
|
81
|
+
router.history = routerHistory;
|
|
82
|
+
|
|
83
|
+
// 浏览器的返回事件触发位于VueRouter的钩子执行和页面渲染之后,这意味着$route.meta.historyFrom必须在页面渲染完之后才具有正确的值
|
|
84
|
+
// if (window.history && window.history.pushState) {
|
|
85
|
+
// window.history.pushState(null, null, document.URL);
|
|
86
|
+
// }
|
|
87
|
+
window.addEventListener('popstate', function () {
|
|
88
|
+
let $route = getCurrentRoute(router);
|
|
89
|
+
if ($route) {
|
|
90
|
+
$route.meta.historyFrom = router.history.state.forward;
|
|
91
|
+
}
|
|
92
|
+
}, false);
|
|
93
|
+
|
|
94
|
+
// 注册离开页面前事件处理支持
|
|
95
|
+
router.$beforeLeaveHandlers = {};
|
|
96
|
+
router.beforeLeave = function (handler) {
|
|
97
|
+
if (typeof handler === 'function') {
|
|
98
|
+
let $route = getCurrentRoute(router);
|
|
99
|
+
let path = $route.path;
|
|
100
|
+
router.$beforeLeaveHandlers[path] = handler;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
router.beforeEach(function (to, from, next) {
|
|
105
|
+
if (typeof window.tnx.router.beforeLeave === 'function') {
|
|
106
|
+
window.tnx.router.beforeLeave(router, from);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let allow = true;
|
|
110
|
+
let beforeLeaveHandler = router.$beforeLeaveHandlers[from.path];
|
|
111
|
+
if (beforeLeaveHandler) {
|
|
112
|
+
if (beforeLeaveHandler(to) === false) {
|
|
113
|
+
allow = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (allow) {
|
|
117
|
+
next();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
router.afterEach(function (to, from) {
|
|
122
|
+
router.prev = from;
|
|
123
|
+
// 前后hash相同,但全路径不同(意味着参数不同),则需要刷新页面,否则页面不会刷新
|
|
124
|
+
if (to.href === from.href && to.fullPath !== from.fullPath) {
|
|
125
|
+
window.location.reload();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
router.back = FunctionUtil.around(router.back, function (back, path) {
|
|
130
|
+
if (!router.prev?.href) { // 没有href,说明当前页面为刷新后进入的第一个页面,无法简单返回
|
|
131
|
+
let $route = getCurrentRoute(router);
|
|
132
|
+
if (!path) { // 未指定默认返回路径,则返回上一级页面
|
|
133
|
+
path = $route.meta.superiorPath;
|
|
134
|
+
}
|
|
135
|
+
path = instantiatePath(path, $route.params);
|
|
136
|
+
if (path) {
|
|
137
|
+
router.replace(path);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
back.call(router);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
router.pushState = function (path) {
|
|
145
|
+
let success = NetUtil.pushState('#' + path);
|
|
146
|
+
if (!success) {
|
|
147
|
+
this.push(path);
|
|
148
|
+
}
|
|
149
|
+
return success;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
router.replaceState = function (path) {
|
|
153
|
+
let success = NetUtil.replaceState('#' + path);
|
|
154
|
+
if (!success) {
|
|
155
|
+
this.replace(path);
|
|
156
|
+
}
|
|
157
|
+
return success;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return router;
|
|
161
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// tnxvue-validator.js
|
|
2
|
+
/**
|
|
3
|
+
* 校验规则转换器,将服务端元数据中的校验规则转换为async-validator组件的规则。
|
|
4
|
+
* async-validator组件详见:https://github.com/yiminghe/async-validator
|
|
5
|
+
*/
|
|
6
|
+
import validator from '@truenewx/tnxcore/src/tnxcore-validator';
|
|
7
|
+
import AsyncValidator from 'async-validator';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* async-validator组件支持的类型清单
|
|
11
|
+
*/
|
|
12
|
+
const ruleTypes = ['string', 'number', 'boolean', 'method', 'regexp', 'integer', 'float', 'array', 'object', 'enum',
|
|
13
|
+
'date', 'url', 'hex', 'email', 'any'];
|
|
14
|
+
|
|
15
|
+
function getRuleType(metaType) {
|
|
16
|
+
if (ruleTypes.contains(metaType)) {
|
|
17
|
+
return metaType;
|
|
18
|
+
}
|
|
19
|
+
switch (metaType) {
|
|
20
|
+
case 'decimal':
|
|
21
|
+
return 'float';
|
|
22
|
+
case 'regex':
|
|
23
|
+
return 'regexp';
|
|
24
|
+
case 'datetime':
|
|
25
|
+
case 'time':
|
|
26
|
+
return 'date';
|
|
27
|
+
}
|
|
28
|
+
return ruleTypes[0];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getRule(validationName, validationValue, fieldMeta) {
|
|
32
|
+
let rule = undefined;
|
|
33
|
+
let fieldCaption = '';
|
|
34
|
+
// 据目前观察,字段格式校验的错误消息均显示在字段旁,无需显示字段名称,未来如果出现不在字段旁显示的场景,再考虑扩展
|
|
35
|
+
// if (fieldMeta && fieldMeta.caption) {
|
|
36
|
+
// fieldCaption = fieldMeta.caption;
|
|
37
|
+
// }
|
|
38
|
+
switch (validationName) {
|
|
39
|
+
case 'required':
|
|
40
|
+
case 'notNull':
|
|
41
|
+
case 'notEmpty':
|
|
42
|
+
case 'notBlank':
|
|
43
|
+
if (validationValue === true) {
|
|
44
|
+
rule = {
|
|
45
|
+
required: true,
|
|
46
|
+
validator(r, fieldValue, callback, source, options) {
|
|
47
|
+
if (validationValue) {
|
|
48
|
+
let blank = fieldValue === undefined || fieldValue === null;
|
|
49
|
+
if (!blank) {
|
|
50
|
+
if (Array.isArray(fieldValue)) {
|
|
51
|
+
blank = fieldValue.length === 0; // 数组长度为0视为空
|
|
52
|
+
} else if (typeof fieldValue === 'string') {
|
|
53
|
+
blank = fieldValue.trim().length === 0; // 字符串去掉两端空格后长度为0视为空
|
|
54
|
+
} else if (typeof fieldValue === 'number') {
|
|
55
|
+
blank = isNaN(fieldValue); // 非法的数字视为空
|
|
56
|
+
} else if (typeof fieldValue === 'object') {
|
|
57
|
+
// 对象非日期,且没有字段的视为空
|
|
58
|
+
blank = !(fieldValue instanceof Date) && Object.keys(fieldValue).length === 0;
|
|
59
|
+
if (!blank) {
|
|
60
|
+
// 可永久的日期对象,非永久且日期值为空时视为空
|
|
61
|
+
if (typeof fieldValue.permanent === 'boolean') {
|
|
62
|
+
blank = !fieldValue.permanent && !fieldValue.value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (blank) {
|
|
68
|
+
let message = validator.getErrorMessage(validationName, fieldCaption);
|
|
69
|
+
return callback(new Error(message));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return callback();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case 'minLength':
|
|
78
|
+
rule = {
|
|
79
|
+
validator(r, fieldValue, callback, source, options) {
|
|
80
|
+
if (typeof validationValue === 'number' && typeof fieldValue === 'string') {
|
|
81
|
+
// 回车符计入长度
|
|
82
|
+
let enterLength = fieldValue.indexOf('\n') < 0 ? 0 : fieldValue.match(/\n/g).length;
|
|
83
|
+
let fieldLength = fieldValue.length + enterLength;
|
|
84
|
+
if (fieldLength < validationValue) {
|
|
85
|
+
let message = validator.getErrorMessage(validationName, fieldCaption,
|
|
86
|
+
validationValue, validationValue - fieldLength);
|
|
87
|
+
return callback(new Error(message));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return callback();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
break;
|
|
94
|
+
case 'maxLength':
|
|
95
|
+
rule = {
|
|
96
|
+
validator(r, fieldValue, callback, source, options) {
|
|
97
|
+
if (typeof validationValue === 'number' && typeof fieldValue === 'string') {
|
|
98
|
+
// 回车符计入长度
|
|
99
|
+
let enterLength = fieldValue.indexOf('\n') < 0 ? 0 : fieldValue.match(/\n/g).length;
|
|
100
|
+
let fieldLength = fieldValue.length + enterLength;
|
|
101
|
+
if (fieldLength > validationValue) {
|
|
102
|
+
let message = validator.getErrorMessage(validationName, fieldCaption,
|
|
103
|
+
validationValue, fieldLength - validationValue);
|
|
104
|
+
return callback(new Error(message));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return callback();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
break;
|
|
111
|
+
case 'number':
|
|
112
|
+
if (validationValue === true) {
|
|
113
|
+
rule = {
|
|
114
|
+
validator(r, fieldValue, callback, source, options) {
|
|
115
|
+
if (fieldValue && typeof fieldValue === 'string') {
|
|
116
|
+
if (!/^[0-9]+$/.test(fieldValue)) {
|
|
117
|
+
let message = validator.getErrorMessage(validationName, fieldCaption);
|
|
118
|
+
return callback(new Error(message));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return callback();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case 'notContainsHtmlChars':
|
|
127
|
+
if (validationValue === true) {
|
|
128
|
+
rule = {
|
|
129
|
+
validator(r, fieldValue, callback, source, options) {
|
|
130
|
+
if (fieldValue) {
|
|
131
|
+
let limitedValues = ['<', '>', '\'', '"', '\\'];
|
|
132
|
+
for (let i = 0; i < limitedValues.length; i++) {
|
|
133
|
+
if (fieldValue.indexOf(limitedValues[i]) >= 0) {
|
|
134
|
+
let s = limitedValues.join(' ');
|
|
135
|
+
let message = validator.getErrorMessage('notContains', fieldCaption, s);
|
|
136
|
+
return callback(new Error(message));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return callback();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
case 'notContainsIllegalFilenameChars':
|
|
146
|
+
if (validationValue === true) {
|
|
147
|
+
rule = {
|
|
148
|
+
validator(r, fieldValue, callback, source, options) {
|
|
149
|
+
if (fieldValue) {
|
|
150
|
+
let limitedValues = ['/', '\\', ':', '*', '?', '"', '<', '>', '|'];
|
|
151
|
+
for (let i = 0; i < limitedValues.length; i++) {
|
|
152
|
+
if (fieldValue.indexOf(limitedValues[i]) >= 0) {
|
|
153
|
+
let s = limitedValues.join(' ');
|
|
154
|
+
let message = validator.getErrorMessage('notContains', fieldCaption, s);
|
|
155
|
+
return callback(new Error(message));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return callback();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
case 'notContains':
|
|
165
|
+
if (validationValue) {
|
|
166
|
+
rule = {
|
|
167
|
+
validator(r, fieldValue, callback, source, options) {
|
|
168
|
+
if (fieldValue) {
|
|
169
|
+
let limitedValues = validationValue.split(' ');
|
|
170
|
+
for (let i = 0; i < limitedValues.length; i++) {
|
|
171
|
+
if (fieldValue.indexOf(limitedValues[i]) >= 0) {
|
|
172
|
+
let message = validator.getErrorMessage('notContains', fieldCaption,
|
|
173
|
+
validationValue);
|
|
174
|
+
return callback(new Error(message));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return callback();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case 'rejectHtmlTags':
|
|
184
|
+
if (validationValue === true) {
|
|
185
|
+
rule = {
|
|
186
|
+
validator(r, fieldValue, callback, source, options) {
|
|
187
|
+
if (fieldValue) {
|
|
188
|
+
if (/<[a-z]+[ ]*[/]?[ ]*>/gi.test(fieldValue)) {
|
|
189
|
+
let message = validator.getErrorMessage(validationName, fieldCaption);
|
|
190
|
+
return callback(new Error(message));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return callback();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case 'allowedHtmlTags':
|
|
199
|
+
if (validationValue) {
|
|
200
|
+
rule = {
|
|
201
|
+
validator(r, fieldValue, callback, source, options) {
|
|
202
|
+
if (fieldValue) {
|
|
203
|
+
let tags = validationValue.toLowerCase().split(',');
|
|
204
|
+
if (tags.length) {
|
|
205
|
+
fieldValue = fieldValue.toLowerCase();
|
|
206
|
+
let leftIndex = fieldValue.indexOf('<');
|
|
207
|
+
let rightIndex = leftIndex >= 0 ? fieldValue.indexOf('>', leftIndex) : -1;
|
|
208
|
+
while (leftIndex >= 0 && rightIndex >= 0) {
|
|
209
|
+
let sub = fieldValue.substring(leftIndex + 1, rightIndex); // <>中间的部分
|
|
210
|
+
let spaceIndex = sub.indexOf(' ');
|
|
211
|
+
let tag = spaceIndex >= 0 ? sub.substring(0, spaceIndex) : sub;
|
|
212
|
+
if (tag.startsWith('/')) { // 标签结束处
|
|
213
|
+
tag = tag.substring(1);
|
|
214
|
+
}
|
|
215
|
+
if (!tags.contains(tag.toLowerCase())) {
|
|
216
|
+
let message = validator.getErrorMessage(validationName, fieldCaption,
|
|
217
|
+
tags.join(', '));
|
|
218
|
+
return callback(new Error(message)); // 存在不允许的标签,则报错
|
|
219
|
+
}
|
|
220
|
+
leftIndex = fieldValue.indexOf('<', rightIndex);
|
|
221
|
+
rightIndex = leftIndex >= 0 ? fieldValue.indexOf('>', leftIndex) : -1;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return callback();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case 'forbiddenHtmlTags':
|
|
231
|
+
if (validationValue) {
|
|
232
|
+
rule = {
|
|
233
|
+
validator(r, fieldValue, callback, source, options) {
|
|
234
|
+
if (fieldValue) {
|
|
235
|
+
let tags = validationValue.toLowerCase().split(',');
|
|
236
|
+
if (tags.length) {
|
|
237
|
+
fieldValue = fieldValue.toLowerCase();
|
|
238
|
+
for (let tag of tags) {
|
|
239
|
+
if (fieldValue.contains('<' + tag + '>') || fieldValue.contains('<' + tag + ' ')) {
|
|
240
|
+
let message = validator.getErrorMessage(validationName, fieldCaption,
|
|
241
|
+
tags.join(', '));
|
|
242
|
+
return callback(new Error(message));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return callback();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
case 'email':
|
|
253
|
+
if (validationValue === true) {
|
|
254
|
+
rule = {
|
|
255
|
+
type: validationName,
|
|
256
|
+
message: validator.getErrorMessage(validationName, fieldCaption),
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
case 'cellphone':
|
|
261
|
+
case 'idCardNo':
|
|
262
|
+
case 'url':
|
|
263
|
+
case 'opposableUrl':
|
|
264
|
+
rule = {
|
|
265
|
+
validator(r, fieldValue, callback, source, options) {
|
|
266
|
+
if (validationValue) {
|
|
267
|
+
let message = validator.validateRegExp(validationName, fieldValue, fieldCaption);
|
|
268
|
+
if (message) {
|
|
269
|
+
return callback(new Error(message));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return callback();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
break;
|
|
276
|
+
case 'regex':
|
|
277
|
+
rule = {
|
|
278
|
+
validator(r, fieldValue, callback, source, options) {
|
|
279
|
+
if (fieldValue) {
|
|
280
|
+
let pattern = validationValue[0];
|
|
281
|
+
// 服务端正则表达式无需以^$作为首尾,客户端需确保以^$作为首尾
|
|
282
|
+
if (!pattern.startsWith('^')) {
|
|
283
|
+
pattern = '^' + pattern;
|
|
284
|
+
}
|
|
285
|
+
if (!pattern.endsWith('$')) {
|
|
286
|
+
pattern += '$';
|
|
287
|
+
}
|
|
288
|
+
let regexp = new RegExp(pattern, 'gi');
|
|
289
|
+
if (!regexp.test(fieldValue)) {
|
|
290
|
+
let message = validationValue[1];
|
|
291
|
+
if (message) {
|
|
292
|
+
message = fieldCaption + message;
|
|
293
|
+
} else {
|
|
294
|
+
message = validator.getErrorMessage('regex', fieldCaption, '');
|
|
295
|
+
}
|
|
296
|
+
return callback(new Error(message));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return callback();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
case 'custom':
|
|
304
|
+
if (typeof validationValue === 'function') {
|
|
305
|
+
rule = {
|
|
306
|
+
validator(r, fieldValue, callback, source, options) {
|
|
307
|
+
let message = validationValue(fieldValue);
|
|
308
|
+
if (message) {
|
|
309
|
+
return callback(new Error(message));
|
|
310
|
+
}
|
|
311
|
+
return callback();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
if (rule) {
|
|
318
|
+
rule.name = validationName;
|
|
319
|
+
let metaType = 'text';
|
|
320
|
+
if (fieldMeta?.type) {
|
|
321
|
+
metaType = fieldMeta.type.toLowerCase();
|
|
322
|
+
}
|
|
323
|
+
rule.type = rule.type || getRuleType(metaType);
|
|
324
|
+
let optional = metaType === 'option' || metaType === 'datetime' || metaType === 'date' || metaType === 'time';
|
|
325
|
+
rule.trigger = optional ? 'change' : 'blur';
|
|
326
|
+
}
|
|
327
|
+
return rule;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 从服务端元数据中构建完整的规则集
|
|
332
|
+
* @param meta
|
|
333
|
+
* @returns {{}}
|
|
334
|
+
*/
|
|
335
|
+
export function getRules(meta) {
|
|
336
|
+
let rules = {};
|
|
337
|
+
Object.keys(meta).forEach(fieldName => {
|
|
338
|
+
let fieldMeta = meta[fieldName];
|
|
339
|
+
if (fieldMeta.validation) {
|
|
340
|
+
let fieldRules = [];
|
|
341
|
+
Object.keys(fieldMeta.validation).forEach(validationName => {
|
|
342
|
+
let validationValue = fieldMeta.validation[validationName];
|
|
343
|
+
let rule = getRule(validationName, validationValue, fieldMeta);
|
|
344
|
+
if (rule) {
|
|
345
|
+
fieldRules.push(rule);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
// 将可能包含的引用字段路径中的.替换为__,以符合async-validator规则名称的规范
|
|
349
|
+
let ruleName = fieldName.replace('.', '__');
|
|
350
|
+
rules[ruleName] = fieldRules;
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
return rules;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export default {
|
|
357
|
+
getErrorMessage: validator.getErrorMessage,
|
|
358
|
+
testRegExp: validator.testRegExp,
|
|
359
|
+
validateRegExp: validator.validateRegExp,
|
|
360
|
+
getRule,
|
|
361
|
+
getRules,
|
|
362
|
+
validate: function (rules, source, callback) {
|
|
363
|
+
return new AsyncValidator(rules).validate(source, callback);
|
|
364
|
+
},
|
|
365
|
+
}
|