@truenewx/tnxvue3 3.0.4 → 3.0.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.
- package/package.json +1 -1
- package/src/bootstrap-vue/button/Button.vue +2 -1
- package/src/bootstrap-vue/dialog/Dialog.vue +3 -3
- package/src/bootstrap-vue/form/Form.vue +76 -23
- package/src/bootstrap-vue/submit-form/SubmitForm.vue +176 -0
- package/src/bootstrap-vue/tnxbsv.js +2 -1
- package/src/tnxvue.js +2 -2
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<BButton class="tnxbsv-button" :loading="loading" :disabled="loading">
|
|
2
|
+
<BButton class="tnxbsv-button" :variant="type || 'outline-secondary'" :loading="loading" :disabled="loading">
|
|
3
3
|
<i class="me-1" :class="icon" v-if="icon"></i>
|
|
4
4
|
<div>
|
|
5
5
|
<slot></slot>
|
|
@@ -21,6 +21,7 @@ export default {
|
|
|
21
21
|
name: 'TnxbsvButton',
|
|
22
22
|
components: {BButton, Loading},
|
|
23
23
|
props: {
|
|
24
|
+
type: String,
|
|
24
25
|
icon: String,
|
|
25
26
|
loading: Boolean,
|
|
26
27
|
},
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
<template #title>
|
|
17
17
|
<div v-html="title" v-if="title"></div>
|
|
18
18
|
</template>
|
|
19
|
-
<div v-html="content" v-if="typeof content === 'string'"></div>
|
|
20
|
-
<component :is="content" v-bind="contentProps" v-else></component>
|
|
19
|
+
<div ref="content" v-html="content" v-if="typeof content === 'string'"></div>
|
|
20
|
+
<component ref="content" :is="content" v-bind="contentProps" v-else></component>
|
|
21
21
|
<template #footer>
|
|
22
22
|
<TnxbsvButton v-for="(button, index) in buttons" :key="index"
|
|
23
|
-
:
|
|
23
|
+
:type="button.type"
|
|
24
24
|
:loading="buttonLoadings[index]"
|
|
25
25
|
@click="btnClick(index)"
|
|
26
26
|
>
|
|
@@ -24,9 +24,20 @@ export default {
|
|
|
24
24
|
type: Object, // key: 字段名,value: 验证规则数组
|
|
25
25
|
default: () => ({}),
|
|
26
26
|
},
|
|
27
|
+
disabled: Boolean,
|
|
27
28
|
},
|
|
28
29
|
data() {
|
|
29
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
fieldEventListeners: {},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
watch: {
|
|
35
|
+
rules() {
|
|
36
|
+
this.initRules();
|
|
37
|
+
},
|
|
38
|
+
disabled() {
|
|
39
|
+
this.updateElementsDisabled();
|
|
40
|
+
},
|
|
30
41
|
},
|
|
31
42
|
mounted() {
|
|
32
43
|
window.addEventListener('resize', this.updateLabelWidth);
|
|
@@ -34,16 +45,18 @@ export default {
|
|
|
34
45
|
setTimeout(() => {
|
|
35
46
|
this.initRules();
|
|
36
47
|
this.updateLabelWidth();
|
|
48
|
+
this.updateElementsDisabled();
|
|
37
49
|
});
|
|
38
50
|
});
|
|
39
51
|
},
|
|
40
52
|
beforeUnmount() {
|
|
41
53
|
window.removeEventListener('resize', this.updateLabelWidth);
|
|
54
|
+
this.clearEventListeners();
|
|
42
55
|
},
|
|
43
56
|
methods: {
|
|
44
57
|
getFieldGroupElements() {
|
|
45
58
|
let elements = {};
|
|
46
|
-
let groups = this.$el.querySelectorAll(
|
|
59
|
+
let groups = this.$el.querySelectorAll('.b-form-group[prop]');
|
|
47
60
|
groups.forEach(label => {
|
|
48
61
|
let fieldName = label.getAttribute('prop');
|
|
49
62
|
if (fieldName) {
|
|
@@ -59,7 +72,34 @@ export default {
|
|
|
59
72
|
}
|
|
60
73
|
return fieldRules;
|
|
61
74
|
},
|
|
75
|
+
addFieldEventListener(fieldName, element, eventType, handler) {
|
|
76
|
+
if (!this.fieldEventListeners[fieldName]) {
|
|
77
|
+
this.fieldEventListeners[fieldName] = {};
|
|
78
|
+
}
|
|
79
|
+
if (!this.fieldEventListeners[fieldName][eventType]) {
|
|
80
|
+
this.fieldEventListeners[fieldName][eventType] = [];
|
|
81
|
+
}
|
|
82
|
+
element.addEventListener(eventType, handler);
|
|
83
|
+
this.fieldEventListeners[fieldName][eventType].push(handler);
|
|
84
|
+
},
|
|
85
|
+
clearEventListeners() {
|
|
86
|
+
for (let fieldName in this.fieldEventListeners) {
|
|
87
|
+
let listeners = this.fieldEventListeners[fieldName];
|
|
88
|
+
for (let eventType in listeners) {
|
|
89
|
+
let handlers = listeners[eventType];
|
|
90
|
+
let fieldElement = this.getFieldElement(fieldName);
|
|
91
|
+
if (fieldElement) {
|
|
92
|
+
handlers.forEach(handler => {
|
|
93
|
+
fieldElement.removeEventListener(eventType, handler);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.fieldEventListeners = {}; // 清空映射表
|
|
99
|
+
},
|
|
62
100
|
initRules() {
|
|
101
|
+
this.clearEventListeners();
|
|
102
|
+
|
|
63
103
|
let fieldGroupElements = this.getFieldGroupElements();
|
|
64
104
|
for (let fieldName in fieldGroupElements) {
|
|
65
105
|
let fieldGroupElement = fieldGroupElements[fieldName];
|
|
@@ -77,12 +117,12 @@ export default {
|
|
|
77
117
|
let fieldElement = fieldGroupElement.querySelector('input, select, textarea');
|
|
78
118
|
if (fieldElement) {
|
|
79
119
|
if (existsChangeValidator) {
|
|
80
|
-
|
|
120
|
+
this.addFieldEventListener(fieldName, fieldElement, 'change', () => {
|
|
81
121
|
this.validateField(fieldName, 'change');
|
|
82
122
|
});
|
|
83
123
|
}
|
|
84
124
|
if (existsBlurValidator) {
|
|
85
|
-
|
|
125
|
+
this.addFieldEventListener(fieldName, fieldElement, 'blur', () => {
|
|
86
126
|
this.validateField(fieldName, 'blur');
|
|
87
127
|
});
|
|
88
128
|
}
|
|
@@ -114,22 +154,24 @@ export default {
|
|
|
114
154
|
this.clearValidate(); // 清除之前的反馈信息
|
|
115
155
|
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
116
156
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
let errors = {};
|
|
159
|
+
let fieldGroupElements = this.getFieldGroupElements();
|
|
160
|
+
for (let fieldName in fieldGroupElements) {
|
|
161
|
+
let fieldErrorMessages = this.validateField(fieldName);
|
|
162
|
+
if (fieldErrorMessages.length) {
|
|
163
|
+
errors[fieldName] = fieldErrorMessages;
|
|
164
|
+
}
|
|
122
165
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return valid;
|
|
166
|
+
if (Object.keys(errors).length) {
|
|
167
|
+
this.$el.classList.add('was-validated'); // 添加已验证样式
|
|
168
|
+
reject(errors);
|
|
169
|
+
}
|
|
170
|
+
resolve();
|
|
171
|
+
});
|
|
130
172
|
},
|
|
131
173
|
getFieldElement(fieldName) {
|
|
132
|
-
let groupElement = this.$el.querySelector(
|
|
174
|
+
let groupElement = this.$el.querySelector(`.b-form-group[prop="${fieldName}"]`);
|
|
133
175
|
if (groupElement) {
|
|
134
176
|
return groupElement.querySelector('input, textarea, select');
|
|
135
177
|
}
|
|
@@ -138,27 +180,26 @@ export default {
|
|
|
138
180
|
validateField(fieldName, trigger) {
|
|
139
181
|
const fieldValue = this.model[fieldName];
|
|
140
182
|
const fieldRules = this.getFieldRules(fieldName);
|
|
141
|
-
const
|
|
183
|
+
const fieldErrorMessages = [];
|
|
142
184
|
|
|
143
185
|
fieldRules.forEach(fieldRule => {
|
|
144
186
|
if (typeof fieldRule.validator === 'function' && (!trigger || trigger === fieldRule.trigger)) {
|
|
145
187
|
fieldRule.validator(fieldRule, fieldValue, (error) => {
|
|
146
188
|
if (error) {
|
|
147
|
-
|
|
189
|
+
fieldErrorMessages.push(error.message);
|
|
148
190
|
}
|
|
149
191
|
});
|
|
150
192
|
}
|
|
151
193
|
// 没有validator()方法,则不进行校验
|
|
152
194
|
});
|
|
153
195
|
|
|
154
|
-
if (
|
|
155
|
-
this.setFieldInvalidFeedback(fieldName,
|
|
156
|
-
return false;
|
|
196
|
+
if (fieldErrorMessages.length) {
|
|
197
|
+
this.setFieldInvalidFeedback(fieldName, fieldErrorMessages);
|
|
157
198
|
} else {
|
|
158
199
|
// 清除错误状态
|
|
159
200
|
this.removeFieldInvalidFeedback(fieldName);
|
|
160
|
-
return true;
|
|
161
201
|
}
|
|
202
|
+
return fieldErrorMessages;
|
|
162
203
|
},
|
|
163
204
|
setFieldInvalidFeedback(fieldName, messages) {
|
|
164
205
|
let fieldElement = this.getFieldElement(fieldName);
|
|
@@ -199,6 +240,18 @@ export default {
|
|
|
199
240
|
const invalidFeedbackElements = this.$el.querySelectorAll('.invalid-feedback');
|
|
200
241
|
invalidFeedbackElements.forEach(el => el.remove());
|
|
201
242
|
},
|
|
243
|
+
scrollToField(fieldName) {
|
|
244
|
+
let fieldElement = this.getFieldElement(fieldName);
|
|
245
|
+
if (fieldElement) {
|
|
246
|
+
fieldElement.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
updateElementsDisabled() {
|
|
250
|
+
let elements = this.$el.querySelectorAll('input, textarea, select, button');
|
|
251
|
+
elements.forEach(element => {
|
|
252
|
+
element.disabled = this.disabled;
|
|
253
|
+
});
|
|
254
|
+
},
|
|
202
255
|
}
|
|
203
256
|
}
|
|
204
257
|
</script>
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TnxbsvForm ref="form" :model="model" :rules="validationRules" :disabled="disabled">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
<TnxbsvFormGroup v-if="submit !== undefined && submit !== null">
|
|
5
|
+
<div class="flex-v-center">
|
|
6
|
+
<TnxbsvButton :type="theme || 'primary'" :size="size" @click="toSubmit" v-if="submit !== false">
|
|
7
|
+
{{ _submitText }}
|
|
8
|
+
</TnxbsvButton>
|
|
9
|
+
<TnxbsvButton class="ms-2" :size="size" @click="toCancel" v-if="cancel !== false">
|
|
10
|
+
{{ cancelText }}
|
|
11
|
+
</TnxbsvButton>
|
|
12
|
+
</div>
|
|
13
|
+
</TnxbsvFormGroup>
|
|
14
|
+
</TnxbsvForm>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import TnxbsvForm from '../form/Form.vue';
|
|
19
|
+
import TnxbsvFormGroup from '../form/FormGroup.vue';
|
|
20
|
+
import TnxbsvButton from '../button/Button.vue';
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'TnxbsvSubmitForm',
|
|
24
|
+
components: {TnxbsvForm, TnxbsvFormGroup, TnxbsvButton},
|
|
25
|
+
props: {
|
|
26
|
+
model: Object,
|
|
27
|
+
rules: [String, Object], // 加载字段校验规则的URL地址,或规则集对象
|
|
28
|
+
rulesApp: { // 加载字段校验规则的应用名称
|
|
29
|
+
type: String,
|
|
30
|
+
default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
|
|
31
|
+
},
|
|
32
|
+
rulesLoaded: Function, // 规则集加载后的附加处理函数,仅在rules为字符串类型的URL地址时有效
|
|
33
|
+
submit: {
|
|
34
|
+
type: [Function, Boolean],
|
|
35
|
+
default: null,
|
|
36
|
+
},
|
|
37
|
+
submitText: String,
|
|
38
|
+
theme: String,
|
|
39
|
+
cancel: {
|
|
40
|
+
type: [String, Function, Boolean],
|
|
41
|
+
default: true
|
|
42
|
+
},
|
|
43
|
+
cancelText: {
|
|
44
|
+
type: String,
|
|
45
|
+
default: '取消'
|
|
46
|
+
},
|
|
47
|
+
errorFocus: {
|
|
48
|
+
type: Boolean,
|
|
49
|
+
default: false,
|
|
50
|
+
},
|
|
51
|
+
size: String,
|
|
52
|
+
},
|
|
53
|
+
emits: ['rules-loaded', 'meta'],
|
|
54
|
+
data() {
|
|
55
|
+
return {
|
|
56
|
+
validationRules: {},
|
|
57
|
+
disabled: false,
|
|
58
|
+
fieldNames: [],
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
computed: {
|
|
62
|
+
_submitText() {
|
|
63
|
+
if (this.submitText) {
|
|
64
|
+
return this.submitText;
|
|
65
|
+
}
|
|
66
|
+
return this.cancel === false ? '保存' : '提交';
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
watch: {
|
|
70
|
+
rules() {
|
|
71
|
+
this.initRules();
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
mounted() {
|
|
75
|
+
this.initRules();
|
|
76
|
+
},
|
|
77
|
+
methods: {
|
|
78
|
+
initRules() {
|
|
79
|
+
if (this.rules) {
|
|
80
|
+
if (typeof this.rules === 'string') {
|
|
81
|
+
window.tnx.app.rpc.getMeta(this.rules, meta => {
|
|
82
|
+
if (this.rulesLoaded) {
|
|
83
|
+
this.rulesLoaded(meta.$rules);
|
|
84
|
+
} else {
|
|
85
|
+
this.$emit('rules-loaded', meta.$rules);
|
|
86
|
+
}
|
|
87
|
+
this.validationRules = meta.$rules;
|
|
88
|
+
delete meta.$rules;
|
|
89
|
+
this.$emit('meta', meta);
|
|
90
|
+
this.fieldNames = Object.keys(meta);
|
|
91
|
+
}, this.rulesApp);
|
|
92
|
+
} else {
|
|
93
|
+
this.validationRules = {};
|
|
94
|
+
Object.keys(this.rules).forEach(fieldName => {
|
|
95
|
+
let fieldRules = this.rules[fieldName] || [];
|
|
96
|
+
if (!Array.isArray(fieldRules)) {
|
|
97
|
+
fieldRules = [fieldRules];
|
|
98
|
+
}
|
|
99
|
+
if (fieldRules.length) {
|
|
100
|
+
this.validationRules[fieldName] = fieldRules.filter((rule, index) => {
|
|
101
|
+
let valid = rule !== undefined && rule !== null;
|
|
102
|
+
if (!valid) {
|
|
103
|
+
console.error(`invalid rules[${index}] for field "${fieldName}": `, rule);
|
|
104
|
+
}
|
|
105
|
+
return valid;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
this.validationRules = {};
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
validate(callback, errorFocus) {
|
|
115
|
+
this.$refs.form.validate().then(() => {
|
|
116
|
+
if (typeof callback === 'function') {
|
|
117
|
+
callback(true);
|
|
118
|
+
}
|
|
119
|
+
}).catch(errors => {
|
|
120
|
+
if (this.errorFocus && errorFocus !== false) {
|
|
121
|
+
this.$nextTick(() => {
|
|
122
|
+
this.focusError.call(this, errors);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (typeof callback === 'function') {
|
|
126
|
+
callback(false);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
focusError(errors) {
|
|
131
|
+
let fieldNames = Object.keys(errors);
|
|
132
|
+
let fieldName = fieldNames[0];
|
|
133
|
+
if (fieldName) {
|
|
134
|
+
let fieldElement = this.$refs.form.getFieldElement(fieldName);
|
|
135
|
+
if (fieldElement) {
|
|
136
|
+
fieldElement.focus();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// 没有找到错误字段输入框,则滚动到错误栏目处
|
|
140
|
+
this.$refs.form.scrollToField(fieldName);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
disable(disabled) {
|
|
144
|
+
this.disabled = disabled !== false;
|
|
145
|
+
},
|
|
146
|
+
toSubmit(callback, disabled) {
|
|
147
|
+
this.validate(success => {
|
|
148
|
+
if (success) {
|
|
149
|
+
if (typeof callback !== 'function') {
|
|
150
|
+
callback = this.submit;
|
|
151
|
+
}
|
|
152
|
+
if (typeof callback === 'function') {
|
|
153
|
+
if (disabled !== false) {
|
|
154
|
+
this.disable();
|
|
155
|
+
}
|
|
156
|
+
callback(this);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
toCancel() {
|
|
162
|
+
if (typeof this.cancel === 'function') {
|
|
163
|
+
this.cancel();
|
|
164
|
+
} else if (typeof this.cancel === 'string') {
|
|
165
|
+
this.$router.back(this.cancel);
|
|
166
|
+
} else if (this.cancel !== false) {
|
|
167
|
+
this.$router.back();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<style>
|
|
175
|
+
|
|
176
|
+
</style>
|
|
@@ -15,12 +15,13 @@ import Loading from './loading/Loading.vue';
|
|
|
15
15
|
import Paged from './paged/Paged.vue';
|
|
16
16
|
import QueryTable from './query-table/QueryTable.vue';
|
|
17
17
|
import Select from './select/Select.vue';
|
|
18
|
+
import SubmitForm from './submit-form/SubmitForm.vue';
|
|
18
19
|
|
|
19
20
|
export const build = tnxvue.build;
|
|
20
21
|
|
|
21
22
|
export default build('tnxbsv', () => {
|
|
22
23
|
const components = Object.assign({}, tnxvue.components, {
|
|
23
|
-
Button, Dialog, EnumSelect, Form, FormGroup, Loading, Paged, QueryTable, Select,
|
|
24
|
+
Button, Dialog, EnumSelect, Form, FormGroup, Loading, Paged, QueryTable, Select, SubmitForm,
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
const tnxbsv = Object.assign({}, tnxjq, tnxvue, {
|
package/src/tnxvue.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 基于Vue 3的扩展支持
|
|
4
4
|
*/
|
|
5
|
-
import tnxcore from '@truenewx/tnxcore';
|
|
6
|
-
|
|
5
|
+
// import tnxcore from '@truenewx/tnxcore';
|
|
6
|
+
import tnxcore from '../../core/src/tnxcore';
|
|
7
7
|
import validator from './tnxvue-validator';
|
|
8
8
|
import createRouter from './tnxvue-router';
|
|
9
9
|
import Text from './text/Text.vue';
|