@truenewx/tnxvue3 3.4.3 → 3.4.5

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 (47) hide show
  1. package/package.json +12 -6
  2. package/src/bootstrap-vue/dialog/Dialog.vue +22 -10
  3. package/src/element-plus/aj-captcha/api/index.js +2 -2
  4. package/src/element-plus/avatar/Avatar.vue +4 -27
  5. package/src/element-plus/date-picker/DatePicker.vue +8 -9
  6. package/src/element-plus/dialog/Dialog.vue +34 -22
  7. package/src/element-plus/drawer/Drawer.vue +22 -5
  8. package/src/element-plus/edit-table/EditTable.vue +10 -10
  9. package/src/element-plus/enum-select/EnumSelect.vue +30 -30
  10. package/src/element-plus/enum-view/EnumView.vue +1 -3
  11. package/src/element-plus/fetch-cascader/FetchCascader.vue +4 -4
  12. package/src/element-plus/fetch-select/FetchSelect.vue +3 -3
  13. package/src/element-plus/fetch-tags/FetchTags.vue +1 -1
  14. package/src/element-plus/fss-upload/FssUpload.vue +76 -115
  15. package/src/element-plus/fss-view/FssView.vue +28 -30
  16. package/src/element-plus/icon/Icon.vue +3 -0
  17. package/src/element-plus/query-form/QueryForm.vue +3 -3
  18. package/src/element-plus/query-table/QueryTable.vue +12 -12
  19. package/src/element-plus/region-cascader/RegionCascader.vue +3 -3
  20. package/src/element-plus/select/Select.vue +56 -56
  21. package/src/element-plus/submit-form/SubmitForm.vue +5 -5
  22. package/src/element-plus/tnxel-validator.ts +347 -0
  23. package/src/element-plus/tnxel.css +0 -8
  24. package/src/element-plus/tnxel.ts +561 -0
  25. package/src/element-plus/transfer/Transfer.vue +2 -2
  26. package/src/element-plus/upload/Upload.vue +68 -70
  27. package/src/tdesign/desktop/tnxtdd.ts +5 -5
  28. package/src/tdesign/mobile/calendar/Calendar.vue +121 -0
  29. package/src/tdesign/mobile/date-time-picker/DateTimePicker.vue +147 -0
  30. package/src/tdesign/mobile/dialog/Dialog.vue +179 -0
  31. package/src/tdesign/mobile/dialog/DialogContent.vue +13 -0
  32. package/src/tdesign/mobile/drawer/Drawer.vue +176 -0
  33. package/src/tdesign/mobile/drawer/DrawerContent.vue +13 -0
  34. package/src/tdesign/mobile/enum-select/EnumSelect.vue +160 -0
  35. package/src/tdesign/mobile/popup/Popup.vue +106 -0
  36. package/src/tdesign/mobile/region-picker/RegionPicker.vue +223 -0
  37. package/src/tdesign/mobile/select/Select.vue +478 -0
  38. package/src/tdesign/mobile/slide-radio-group/SlideRadioGroup.vue +392 -0
  39. package/src/tdesign/mobile/tnxtdm.css +132 -0
  40. package/src/tdesign/mobile/tnxtdm.ts +310 -7
  41. package/src/tdesign/{foundation/validator.ts → tnxtd-validator.ts} +18 -17
  42. package/src/tdesign/tnxtd.css +66 -0
  43. package/src/tdesign/tnxtd.ts +10 -7
  44. package/src/tnxvue-router.ts +65 -18
  45. package/src/tnxvue.ts +71 -31
  46. package/tsconfig.json +33 -19
  47. package/src/element-plus/tnxel.js +0 -598
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <el-checkbox-group class="tnxel-select tnxel-checkbox-group" v-model="model" :theme="theme" :size="size"
3
- :disabled="disabled" :key="groupKey" v-if="selector === 'checkbox'">
3
+ :disabled="disabled" :key="`checkbox-${groupKey}`" v-if="selector === 'checkbox'">
4
4
  <template v-if="items">
5
5
  <el-checkbox v-for="item in items" :key="item[valueName]" :value="item[valueName]"
6
- :data-value="item[valueName]"
7
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
8
- :title="typeof item.disabled === 'string' ? item.disabled : item.title">
6
+ :data-value="item[valueName]"
7
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
8
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title">
9
9
  <slot name="option" :item="item" v-if="$slots.option"></slot>
10
10
  <template v-else>
11
11
  <i :class="item[iconName]" v-if="item[iconName]"></i>
@@ -19,17 +19,17 @@
19
19
  </template>
20
20
  <tnxel-icon value="Loading" v-else/>
21
21
  </el-checkbox-group>
22
- <div class="tnxel-select tnxel-tag-group d-flex flex-wrap" :key="groupKey"
23
- v-else-if="selector === 'tag' || selector === 'tags'">
22
+ <div class="tnxel-select tnxel-tag-group d-flex flex-wrap" :key="`tag-${groupKey}`"
23
+ v-else-if="selector === 'tag' || selector === 'tags'">
24
24
  <template v-if="items">
25
25
  <el-button link :size="size" :class="emptyClass" v-if="emptyText" @click="clear">
26
26
  {{ emptyText }}
27
27
  </el-button>
28
28
  <el-tag v-for="item in items" :key="item[valueName]" :type="theme"
29
- :effect="isSelected(item[valueName]) ? 'dark' : 'plain'" :data-value="item[valueName]"
30
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
31
- :title="typeof item.disabled === 'string' ? item.disabled : item.title"
32
- @click="select(item[valueName])">
29
+ :effect="isSelected(item[valueName]) ? 'dark' : 'plain'" :data-value="item[valueName]"
30
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
31
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title"
32
+ @click="select(item[valueName])">
33
33
  <slot name="option" :item="item" v-if="$slots.option"></slot>
34
34
  <template v-else>
35
35
  <i :class="item[iconName]" v-if="item[iconName]"></i>
@@ -42,18 +42,18 @@
42
42
  </template>
43
43
  <tnxel-icon value="Loading" v-else/>
44
44
  </div>
45
- <div class="tnxel-select tnxel-text-button-group d-flex flex-wrap" :key="groupKey"
46
- v-else-if="selector === 'text' || selector === 'texts'">
45
+ <div class="tnxel-select tnxel-text-button-group d-flex flex-wrap" :key="`text-${groupKey}`"
46
+ v-else-if="selector === 'text' || selector === 'texts'">
47
47
  <template v-if="items">
48
48
  <el-button link :size="size" :class="emptyClass" v-if="emptyText" @click="clear">
49
49
  {{ emptyText }}
50
50
  </el-button>
51
51
  <el-button v-for="item in items" :key="item[valueName]"
52
- :link="!isSelected(item[valueName])" :plain="isSelected(item[valueName])"
53
- :size="size" :data-value="item[valueName]"
54
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
55
- :title="typeof item.disabled === 'string' ? item.disabled : item.title"
56
- @click="select(item[valueName], $event)">
52
+ :link="!isSelected(item[valueName])" :plain="isSelected(item[valueName])"
53
+ :size="size" :data-value="item[valueName]"
54
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
55
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title"
56
+ @click="select(item[valueName], $event)">
57
57
  <slot name="option" :item="item" v-if="$slots.option"></slot>
58
58
  <template v-else>
59
59
  <i :class="item[iconName]" v-if="item[iconName]"></i>
@@ -67,12 +67,12 @@
67
67
  <tnxel-icon value="Loading" v-else/>
68
68
  </div>
69
69
  <el-radio-group class="tnxel-select tnxel-radio-group ignore-feedback" v-model="model" :theme="theme" :size="size"
70
- :disabled="disabled" :key="groupKey" v-else-if="selector === 'radio'">
70
+ :disabled="disabled" :key="`radio-${groupKey}`" v-else-if="selector === 'radio'">
71
71
  <el-radio :value="emptyValue" :class="emptyClass" :border="border" v-if="empty">{{ emptyText }}</el-radio>
72
72
  <el-radio v-for="item in items" :key="item[valueName]" :value="item[valueName]" :data-value="item[valueName]"
73
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
74
- :title="typeof item.disabled === 'string' ? item.disabled : item.title"
75
- :border="border">
73
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
74
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title"
75
+ :border="border">
76
76
  <slot name="option" :item="item" v-if="$slots.option"></slot>
77
77
  <template v-else>
78
78
  <i :class="item[iconName]" v-if="item[iconName]"></i>
@@ -81,12 +81,12 @@
81
81
  </el-radio>
82
82
  </el-radio-group>
83
83
  <el-radio-group class="tnxel-select tnxel-radio-group ignore-feedback" v-model="model" :theme="theme" :size="size"
84
- :disabled="disabled" :key="groupKey" v-else-if="selector === 'radio-button'">
84
+ :disabled="disabled" :key="`radio-button-${groupKey}`" v-else-if="selector === 'radio-button'">
85
85
  <el-radio-button :class="emptyClass" :value="emptyValue" v-if="empty">{{ emptyText }}</el-radio-button>
86
86
  <el-radio-button v-for="item in items" :key="item[valueName]" :value="item[valueName]"
87
- :data-value="item[valueName]"
88
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
89
- :title="typeof item.disabled === 'string' ? item.disabled : item.title">
87
+ :data-value="item[valueName]"
88
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
89
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title">
90
90
  <slot name="option" :item="item" v-if="$slots.option"></slot>
91
91
  <template v-else>
92
92
  <i :class="item[iconName]" v-if="item[iconName]"></i>
@@ -95,12 +95,12 @@
95
95
  </el-radio-button>
96
96
  </el-radio-group>
97
97
  <el-dropdown class="tnxel-select tnxel-dropdown"
98
- popper-class="tnxel-select-dropdown-popper"
99
- trigger="click"
100
- :key="groupKey"
101
- :size="size"
102
- @command="onDropdownCommand"
103
- v-else-if="selector === 'dropdown'">
98
+ popper-class="tnxel-select-dropdown-popper"
99
+ trigger="click"
100
+ :key="`dropdown-${groupKey}`"
101
+ :size="size"
102
+ @command="onDropdownCommand"
103
+ v-else-if="selector === 'dropdown'">
104
104
  <el-button style="max-width: 100%" :type="theme" :title="currentText">
105
105
  <span class="flex-1 overflow-ellipsis me-2">{{ currentText }}</span>
106
106
  <tnxel-icon value="ArrowDown"/>
@@ -108,11 +108,11 @@
108
108
  <template #dropdown v-if="items && items.length">
109
109
  <el-dropdown-menu>
110
110
  <el-dropdown-item v-for="item in items" :key="item[valueName]"
111
- :command="item[valueName]"
112
- :data-value="item[valueName]"
113
- :class="{'selected': isSelected(item[valueName])}"
114
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
115
- :title="typeof item.disabled === 'string' ? item.disabled : item.title"
111
+ :command="item[valueName]"
112
+ :data-value="item[valueName]"
113
+ :class="{'selected': isSelected(item[valueName])}"
114
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
115
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title"
116
116
  >
117
117
  <slot name="option" :item="item" v-if="$slots.option"></slot>
118
118
  <template v-else>
@@ -124,23 +124,23 @@
124
124
  </template>
125
125
  </el-dropdown>
126
126
  <el-dropdown class="tnxel-select tnxel-split-dropdown"
127
- popper-class="tnxel-select-dropdown-popper"
128
- trigger="click"
129
- :key="groupKey"
130
- :type="theme"
131
- :size="size"
132
- split-button
133
- @command="onDropdownCommand"
134
- v-else-if="selector === 'split-dropdown'">
127
+ popper-class="tnxel-select-dropdown-popper"
128
+ trigger="click"
129
+ :key="`split-dropdown-${groupKey}`"
130
+ :type="theme"
131
+ :size="size"
132
+ split-button
133
+ @command="onDropdownCommand"
134
+ v-else-if="selector === 'split-dropdown'">
135
135
  <span>{{ currentText }}</span>
136
136
  <template #dropdown v-if="items && items.length">
137
137
  <el-dropdown-menu>
138
138
  <el-dropdown-item v-for="item in items" :key="item[valueName]"
139
- :command="item[valueName]"
140
- :data-value="item[valueName]"
141
- :class="{'selected': isSelected(item[valueName])}"
142
- :disabled="item.disabled === true || typeof item.disabled === 'string'"
143
- :title="typeof item.disabled === 'string' ? item.disabled : item.title"
139
+ :command="item[valueName]"
140
+ :data-value="item[valueName]"
141
+ :class="{'selected': isSelected(item[valueName])}"
142
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
143
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title"
144
144
  >
145
145
  <slot name="option" :item="item" v-if="$slots.option"></slot>
146
146
  <template v-else>
@@ -152,16 +152,17 @@
152
152
  </template>
153
153
  </el-dropdown>
154
154
  <el-select class="tnxel-select ignore-feedback" v-model="model" :placeholder="placeholder" :theme="theme"
155
- :size="size" :disabled="disabled" :filterable="filterable" :filter-method="filter"
156
- :multiple="selector === 'multi-select'" :key="groupKey" v-else>
155
+ :size="size" :disabled="disabled" :filterable="filterable" :filter-method="filter"
156
+ :multiple="selector === 'multi-select'" :key="`select-${groupKey}`" v-else>
157
157
  <el-option class="text-secondary" :value="emptyValue" :class="emptyClass" v-if="empty">
158
158
  {{ emptyText }}
159
159
  </el-option>
160
160
  <template v-for="item in items">
161
161
  <el-option :key="item[valueName]" :value="item[valueName]" :label="item[textName]"
162
- :data-value="item[valueName]" :disabled="item.disabled === true || typeof item.disabled === 'string'"
163
- :title="typeof item.disabled === 'string' ? item.disabled : item.title"
164
- v-if="!hiddenValues.contains(item[valueName])">
162
+ :data-value="item[valueName]"
163
+ :disabled="item.disabled === true || typeof item.disabled === 'string'"
164
+ :title="typeof item.disabled === 'string' ? item.disabled : item.title"
165
+ v-if="!hiddenValues.contains(item[valueName])">
165
166
  <slot name="option" :item="item" v-if="$slots.option"></slot>
166
167
  <span v-else>{{ item[textName] }}</span>
167
168
  </el-option>
@@ -171,6 +172,7 @@
171
172
 
172
173
  <script>
173
174
  import Icon from '../icon/Icon.vue';
175
+ import {util} from '../tnxel.ts';
174
176
 
175
177
  export const isMultiSelector = function (selector) {
176
178
  return selector === 'checkbox' || selector === 'tags' || selector === 'multi-select' || selector === 'texts';
@@ -267,7 +269,6 @@ export default {
267
269
  if (!this.modelEqualsModelValue) {
268
270
  this.$emit('update:modelValue', newValue);
269
271
  // 新旧值不同时为空才触发变更事件
270
- const util = window.tnx.util;
271
272
  if (util.object.isNotEmpty(newValue) || util.object.isNotEmpty(oldValue)) {
272
273
  let vm = this;
273
274
  // 确保变更事件在值变更应用后再触发
@@ -327,7 +328,6 @@ export default {
327
328
  return item ? item[this.textName] : undefined;
328
329
  },
329
330
  getModel(items) {
330
- const util = window.tnx.util;
331
331
  let model = this.modelValue;
332
332
  if (util.object.isNull(model)) {
333
333
  model = this.defaultValue;
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <el-form ref="form" :id="id" :label-width="labelWidth" :label-position="vertical ? 'top' : 'right'"
3
- :model="model" :rules="validationRules" :validate-on-rule-change="false"
4
- :inline="inline" :inline-message="!vertical" :disabled="disabled"
5
- :class="theme ? ('theme-' + theme) : null" :size="size" :status-icon="statusIcon">
3
+ :model="model" :rules="validationRules" :validate-on-rule-change="false"
4
+ :inline="inline" :inline-message="!vertical" :disabled="disabled"
5
+ :class="theme ? ('theme-' + theme) : null" :size="size" :status-icon="statusIcon">
6
6
  <slot></slot>
7
7
  <el-form-item class="w-100 mb-0" :label="vertical ? undefined : '&nbsp;'"
8
- v-if="submit !== undefined && submit !== null">
8
+ v-if="submit !== undefined && submit !== null">
9
9
  <el-button :type="theme || 'primary'" :size="size" @click="toSubmit" v-if="submit !== false">
10
10
  {{ _submitText }}
11
11
  </el-button>
@@ -111,7 +111,7 @@ export default {
111
111
  initRules() {
112
112
  if (this.rules) {
113
113
  if (typeof this.rules === 'string') {
114
- window.tnx.app.rpc.getMeta(this.rules, this.rulesApp).then(meta => {
114
+ window.tnx.meta.getMeta(this.rules, this.rulesApp).then(meta => {
115
115
  if (this.rulesLoaded) {
116
116
  this.rulesLoaded(meta.$rules);
117
117
  } else {
@@ -0,0 +1,347 @@
1
+ /**
2
+ * 校验规则转换器,将服务端元数据中的校验规则转换为async-validator组件的规则。
3
+ * async-validator组件详见:https://github.com/yiminghe/async-validator
4
+ */
5
+ import AsyncValidator, {RuleType, Rules} from 'async-validator';
6
+ import {FormItemRule} from 'element-plus';
7
+ import {Validator} from '../../../tnxcore/src/tnxcore.ts';
8
+ import {ApiModelPropertyMeta, ApiModelMeta} from '../../../tnxcore/src/api/meta.ts';
9
+
10
+ /**
11
+ * async-validator组件支持的类型清单
12
+ */
13
+ const ruleTypes = ['string', 'number', 'boolean', 'method', 'regexp', 'integer', 'float', 'array', 'object', 'enum',
14
+ 'date', 'url', 'hex', 'email', 'any'];
15
+
16
+ function getRuleType(metaType: string): RuleType {
17
+ if (ruleTypes.contains(metaType)) {
18
+ return metaType as RuleType;
19
+ }
20
+ switch (metaType) {
21
+ case 'decimal':
22
+ return 'float';
23
+ case 'regex':
24
+ return 'regexp';
25
+ }
26
+ return ruleTypes[0] as RuleType;
27
+ }
28
+
29
+ export default class TnxElValidator extends Validator {
30
+
31
+ getRule(validationName: string, validationValue: any, fieldMeta?: ApiModelPropertyMeta): FormItemRule | undefined {
32
+ let rule: FormItemRule;
33
+ let fieldCaption = '';
34
+ // 据目前观察,字段格式校验的错误消息均显示在字段旁,无需显示字段名称,未来如果出现不在字段旁显示的场景,再考虑扩展
35
+ // if (fieldMeta && fieldMeta.caption) {
36
+ // fieldCaption = fieldMeta.caption;
37
+ // }
38
+ let _this = this;
39
+ switch (validationName) {
40
+ case 'required':
41
+ case 'notNull':
42
+ case 'notEmpty':
43
+ case 'notBlank':
44
+ if (validationValue === true) {
45
+ rule = {
46
+ required: true,
47
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
48
+ if (validationValue) {
49
+ let blank = fieldValue === undefined || fieldValue === null;
50
+ if (!blank) {
51
+ if (Array.isArray(fieldValue)) {
52
+ blank = fieldValue.length === 0; // 数组长度为0视为空
53
+ } else if (typeof fieldValue === 'string') {
54
+ blank = fieldValue.trim().length === 0; // 字符串去掉两端空格后长度为0视为空
55
+ } else if (typeof fieldValue === 'number') {
56
+ blank = isNaN(fieldValue); // 非法的数字视为空
57
+ } else if (typeof fieldValue === 'object') {
58
+ // 对象非日期,且没有字段的视为空
59
+ blank = !(fieldValue instanceof Date) && Object.keys(fieldValue).length === 0;
60
+ if (!blank) {
61
+ // 可永久的日期对象,非永久且日期值为空时视为空
62
+ if (typeof fieldValue.permanent === 'boolean') {
63
+ blank = !fieldValue.permanent && !fieldValue.value;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ if (blank) {
69
+ let message = _this.getErrorMessage(validationName, fieldCaption);
70
+ return callback(new Error(message));
71
+ }
72
+ }
73
+ return callback();
74
+ }
75
+ }
76
+ }
77
+ break;
78
+ case 'maxLength':
79
+ rule = {
80
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
81
+ if (typeof validationValue === 'number' && typeof fieldValue === 'string') {
82
+ // 回车符计入长度
83
+ let enterLength = fieldValue.indexOf('\n') < 0 ? 0 : fieldValue.match(/\n/g).length;
84
+ let fieldLength = fieldValue.length + enterLength;
85
+ if (fieldLength > validationValue) {
86
+ let message = _this.getErrorMessage(validationName, fieldCaption,
87
+ validationValue, fieldLength - validationValue);
88
+ return callback(new Error(message));
89
+ }
90
+ }
91
+ return callback();
92
+ }
93
+ };
94
+ break;
95
+ case 'number':
96
+ if (validationValue === true) {
97
+ rule = {
98
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
99
+ if (fieldValue && typeof fieldValue === 'string') {
100
+ if (!/^[0-9]+$/.test(fieldValue)) {
101
+ let message = _this.getErrorMessage(validationName, fieldCaption);
102
+ return callback(new Error(message));
103
+ }
104
+ }
105
+ return callback();
106
+ }
107
+ };
108
+ }
109
+ break;
110
+ case 'notContainsHtmlChars':
111
+ if (validationValue === true) {
112
+ rule = {
113
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
114
+ if (typeof fieldValue === 'string') {
115
+ let limitedValues = ['<', '>', '\'', '"', '\\'];
116
+ for (let i = 0; i < limitedValues.length; i++) {
117
+ if (fieldValue.indexOf(limitedValues[i]) >= 0) {
118
+ let s = limitedValues.join(' ');
119
+ let message = _this.getErrorMessage('notContains', fieldCaption, s);
120
+ return callback(new Error(message));
121
+ }
122
+ }
123
+ }
124
+ return callback();
125
+ }
126
+ };
127
+ }
128
+ break;
129
+ case 'notContains':
130
+ if (validationValue) {
131
+ rule = {
132
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
133
+ if (typeof fieldValue === 'string') {
134
+ let limitedValues = validationValue.split(' ');
135
+ for (let i = 0; i < limitedValues.length; i++) {
136
+ if (fieldValue.indexOf(limitedValues[i]) >= 0) {
137
+ let message = _this.getErrorMessage('notContains', fieldCaption,
138
+ validationValue);
139
+ return callback(new Error(message));
140
+ }
141
+ }
142
+ }
143
+ return callback();
144
+ }
145
+ };
146
+ }
147
+ break;
148
+ case 'rejectHtmlTags':
149
+ if (validationValue === true) {
150
+ rule = {
151
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
152
+ if (fieldValue) {
153
+ if (/<[a-z]+[ ]*[/]?[ ]*>/gi.test(fieldValue)) {
154
+ let message = _this.getErrorMessage(validationName, fieldCaption);
155
+ return callback(new Error(message));
156
+ }
157
+ }
158
+ return callback();
159
+ }
160
+ };
161
+ }
162
+ break;
163
+ case 'allowedHtmlTags':
164
+ if (validationValue) {
165
+ rule = {
166
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
167
+ if (typeof fieldValue === 'string') {
168
+ let tags = validationValue.toLowerCase().split(',');
169
+ if (tags.length) {
170
+ fieldValue = fieldValue.toLowerCase();
171
+ let leftIndex = fieldValue.indexOf('<');
172
+ let rightIndex = leftIndex >= 0 ? fieldValue.indexOf('>', leftIndex) : -1;
173
+ while (leftIndex >= 0 && rightIndex >= 0) {
174
+ let sub = fieldValue.substring(leftIndex + 1, rightIndex); // <>中间的部分
175
+ let spaceIndex = sub.indexOf(' ');
176
+ let tag = spaceIndex >= 0 ? sub.substring(0, spaceIndex) : sub;
177
+ if (tag.startsWith('/')) { // 标签结束处
178
+ tag = tag.substring(1);
179
+ }
180
+ if (!tags.contains(tag.toLowerCase())) {
181
+ let message = _this.getErrorMessage(validationName, fieldCaption,
182
+ tags.join(', '));
183
+ return callback(new Error(message)); // 存在不允许的标签,则报错
184
+ }
185
+ leftIndex = fieldValue.indexOf('<', rightIndex);
186
+ rightIndex = leftIndex >= 0 ? fieldValue.indexOf('>', leftIndex) : -1;
187
+ }
188
+ }
189
+ }
190
+ return callback();
191
+ }
192
+ };
193
+ }
194
+ break;
195
+ case 'forbiddenHtmlTags':
196
+ if (validationValue) {
197
+ rule = {
198
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
199
+ if (fieldValue) {
200
+ let tags = validationValue.toLowerCase().split(',');
201
+ if (tags.length) {
202
+ fieldValue = fieldValue.toLowerCase();
203
+ for (let tag of tags) {
204
+ if (fieldValue.contains('<' + tag + '>') || fieldValue.contains('<' + tag + ' ')) {
205
+ let message = _this.getErrorMessage(validationName, fieldCaption,
206
+ tags.join(', '));
207
+ return callback(new Error(message));
208
+ }
209
+ }
210
+ }
211
+ }
212
+ return callback();
213
+ }
214
+ };
215
+ }
216
+ break;
217
+ case 'email':
218
+ if (validationValue === true) {
219
+ rule = {
220
+ type: validationName,
221
+ message: _this.getErrorMessage(validationName, fieldCaption),
222
+ }
223
+ }
224
+ break;
225
+ case 'idCardNo':
226
+ rule = {
227
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
228
+ if (validationValue) {
229
+ let message = _this.validateRegExp(validationName, fieldValue, fieldCaption);
230
+ if (message) {
231
+ return callback(new Error(message));
232
+ }
233
+ }
234
+ return callback();
235
+ }
236
+ };
237
+ break;
238
+ case 'url':
239
+ rule = {
240
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
241
+ if (validationValue) {
242
+ let message = _this.validateRegExp(validationName, fieldValue, fieldCaption);
243
+ if (message) {
244
+ return callback(new Error(message));
245
+ }
246
+ }
247
+ return callback();
248
+ }
249
+ };
250
+ break;
251
+ case 'opposableUrl':
252
+ rule = {
253
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
254
+ if (validationValue) {
255
+ let message = _this.validateRegExp(validationName, fieldValue, fieldCaption);
256
+ if (message) {
257
+ return callback(new Error(message));
258
+ }
259
+ }
260
+ return callback();
261
+ }
262
+ };
263
+ break;
264
+ case 'regex':
265
+ rule = {
266
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
267
+ if (fieldValue) {
268
+ let pattern = validationValue.pattern;
269
+ // 服务端正则表达式无需以^$作为首尾,客户端需确保以^$作为首尾
270
+ if (!pattern.startsWith('^')) {
271
+ pattern = '^' + pattern;
272
+ }
273
+ if (!pattern.endsWith('$')) {
274
+ pattern += '$';
275
+ }
276
+ let regexp = new RegExp(pattern, 'gi');
277
+ if (!regexp.test(fieldValue)) {
278
+ let message = validationValue.message;
279
+ if (message) {
280
+ message = fieldCaption + message;
281
+ } else {
282
+ message = _this.getErrorMessage('regex', fieldCaption, '');
283
+ }
284
+ return callback(new Error(message));
285
+ }
286
+ }
287
+ return callback();
288
+ }
289
+ }
290
+ break;
291
+ case 'custom':
292
+ if (typeof validationValue === 'function') {
293
+ rule = {
294
+ validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) {
295
+ let message = validationValue(fieldValue);
296
+ if (message) {
297
+ return callback(new Error(message));
298
+ }
299
+ return callback();
300
+ }
301
+ }
302
+ }
303
+ break;
304
+ }
305
+ if (rule) {
306
+ let metaType = 'text';
307
+ if (fieldMeta && fieldMeta.type) {
308
+ metaType = fieldMeta.type.toLowerCase();
309
+ }
310
+ rule.type = rule.type || getRuleType(metaType);
311
+ rule.trigger = metaType === 'option' ? 'change' : 'blur';
312
+ }
313
+ return rule;
314
+ }
315
+
316
+ getRules(meta: ApiModelMeta): Record<string, FormItemRule[]> {
317
+ let rules: Record<string, FormItemRule[]> = {};
318
+ Object.keys(meta).forEach(fieldName => {
319
+ let fieldMeta = meta[fieldName];
320
+ if (fieldMeta.validation) {
321
+ let fieldRules: FormItemRule[] = [];
322
+ Object.keys(fieldMeta.validation).forEach((validationName: string) => {
323
+ let validationValue = fieldMeta.validation[validationName];
324
+ let rule = this.getRule(validationName, validationValue, fieldMeta);
325
+ if (rule) {
326
+ fieldRules.push(rule);
327
+ }
328
+ });
329
+ // 将可能包含的引用字段路径中的.替换为__,以符合规则名称规范
330
+ let ruleName = fieldName.replace('.', '__');
331
+ rules[ruleName] = fieldRules;
332
+ }
333
+ });
334
+ return rules;
335
+ }
336
+
337
+ /**
338
+ * 校验字段
339
+ * @param rules 包含所有字段校验规则的对象,格式:{字段名: [规则数组]}
340
+ * @param model 待校验的模型对象,包含待校验的字段
341
+ * @returns {Promise} then({errors,fields}=>{})
342
+ */
343
+ validate(rules: Rules, model: Record<string, any>): Promise<Record<string, any>> {
344
+ return new AsyncValidator(rules).validate(model);
345
+ }
346
+
347
+ }
@@ -10,14 +10,6 @@
10
10
  background-color: var(--el-color-info-light-8) !important;
11
11
  }
12
12
 
13
- .text-white {
14
- color: white !important;
15
- }
16
-
17
- .bg-white {
18
- background-color: white !important;
19
- }
20
-
21
13
  .text-regular {
22
14
  color: var(--el-text-color-regular) !important;
23
15
  }