@truenewx/tnxvue3 3.0.4 → 3.0.6
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 +2 -2
- package/src/bootstrap-vue/button/Button.vue +5 -4
- package/src/bootstrap-vue/cascader/Cascader.vue +451 -0
- package/src/bootstrap-vue/dialog/Dialog.vue +163 -158
- package/src/bootstrap-vue/form/Form.vue +124 -61
- package/src/bootstrap-vue/form/FormGroup.vue +4 -0
- package/src/bootstrap-vue/{loading/Loading.vue → loading-icon/LoadingIcon.vue} +2 -2
- package/src/bootstrap-vue/loading-overlay/LoadingOverlay.vue +60 -0
- package/src/bootstrap-vue/region-cascader/RegionCascader.vue +119 -0
- package/src/bootstrap-vue/select/Select.vue +19 -6
- package/src/bootstrap-vue/submit-form/SubmitForm.vue +176 -0
- package/src/bootstrap-vue/tags-input/TagsInput.vue +64 -0
- package/src/bootstrap-vue/tnxbsv.css +36 -0
- package/src/bootstrap-vue/tnxbsv.js +111 -2
- package/src/element-plus/tnxel.js +2 -3
|
@@ -1,158 +1,163 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<BModal
|
|
3
|
-
dialog-class="tnxbsv-dialog"
|
|
4
|
-
header-class="tnxbsv-dialog-header"
|
|
5
|
-
title-class="tnxbsv-dialog-title"
|
|
6
|
-
body-class="tnxbsv-dialog-body"
|
|
7
|
-
:footer-class="buttons?.length ? 'tnxbsv-dialog-footer' : 'd-none'"
|
|
8
|
-
:data-v-id="id"
|
|
9
|
-
v-model="visible"
|
|
10
|
-
scrollable
|
|
11
|
-
no-close-on-backdrop
|
|
12
|
-
no-close-on-esc
|
|
13
|
-
:teleport-to="container"
|
|
14
|
-
@hidden="onHidden"
|
|
15
|
-
>
|
|
16
|
-
<template #title>
|
|
17
|
-
<div v-html="title" v-if="title"></div>
|
|
18
|
-
</template>
|
|
19
|
-
<div v-html="content" v-if="typeof content === 'string'"></div>
|
|
20
|
-
<component :is="content" v-bind="contentProps" v-else></component>
|
|
21
|
-
<template #footer>
|
|
22
|
-
<TnxbsvButton v-for="(button, index) in buttons" :key="index"
|
|
23
|
-
:
|
|
24
|
-
:loading="buttonLoadings[index]"
|
|
25
|
-
@click="btnClick(index)"
|
|
26
|
-
>
|
|
27
|
-
{{ button.caption || button.text }}
|
|
28
|
-
</TnxbsvButton>
|
|
29
|
-
</template>
|
|
30
|
-
</BModal>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<script>
|
|
34
|
-
import {BModal} from 'bootstrap-vue-next';
|
|
35
|
-
import TnxbsvButton from '../button/Button.vue'
|
|
36
|
-
|
|
37
|
-
export default {
|
|
38
|
-
name: 'TnxbsvDialog',
|
|
39
|
-
components: {BModal, TnxbsvButton,},
|
|
40
|
-
props: {
|
|
41
|
-
id: {
|
|
42
|
-
type: [String, Number],
|
|
43
|
-
default: () => window.tnx.util.string.uuid32(),
|
|
44
|
-
},
|
|
45
|
-
modelValue: {
|
|
46
|
-
type: Boolean,
|
|
47
|
-
default: false,
|
|
48
|
-
},
|
|
49
|
-
container: String,
|
|
50
|
-
title: String,
|
|
51
|
-
content: [String, Object],
|
|
52
|
-
contentProps: {
|
|
53
|
-
type: Object,
|
|
54
|
-
default: () => ({}),
|
|
55
|
-
},
|
|
56
|
-
buttons: Array,
|
|
57
|
-
theme: String,
|
|
58
|
-
width: {
|
|
59
|
-
type: [Number, String],
|
|
60
|
-
default: 512,
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
emits: ['update:modelValue', 'close'],
|
|
64
|
-
data() {
|
|
65
|
-
return {
|
|
66
|
-
visible: this.modelValue,
|
|
67
|
-
buttonLoadings: [],
|
|
68
|
-
heightChangeObserver: null,
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
watch: {
|
|
72
|
-
modelValue(newValue, oldValue) {
|
|
73
|
-
this.visible = this.modelValue;
|
|
74
|
-
if (newValue && !oldValue) { // 从隐藏到显示
|
|
75
|
-
this.$nextTick(() => {
|
|
76
|
-
this.locate(true);
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
visible() {
|
|
81
|
-
this.$emit('update:modelValue', this.visible);
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
mounted() {
|
|
85
|
-
window.tnx.app.eventBus.once('tnx.error', options => {
|
|
86
|
-
this.buttonLoadings = [];
|
|
87
|
-
});
|
|
88
|
-
this.$nextTick(() => {
|
|
89
|
-
if (this.visible) {
|
|
90
|
-
this.locate(true);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
beforeUnmount() {
|
|
95
|
-
window.tnx.app.eventBus.off('tnx.error');
|
|
96
|
-
},
|
|
97
|
-
methods: {
|
|
98
|
-
locate(observe) {
|
|
99
|
-
const $ = window.tnx.libs.$;
|
|
100
|
-
let $dialog = $(`${this.container} .tnxbsv-dialog`);
|
|
101
|
-
if ($dialog.length) {
|
|
102
|
-
let top = window.tnx.util.dom.getTopVerticallyCenteredOnPage($dialog[0]);
|
|
103
|
-
let width = typeof this.width === 'number' ? this.width + 'px' : this.width;
|
|
104
|
-
$dialog.css({
|
|
105
|
-
'margin-top': top + 'px',
|
|
106
|
-
'width': width,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
if (observe) {
|
|
110
|
-
this.heightChangeObserver = window.tnx.util.dom.observeHeightChange($dialog[0], this.locate);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
btnClick(index) {
|
|
115
|
-
const button = this.buttons[index];
|
|
116
|
-
if (button && typeof button.click === 'function') {
|
|
117
|
-
let result = button.click.call(this.$refs.content, this.close);
|
|
118
|
-
if (result === 'loading') {
|
|
119
|
-
this.buttonLoadings[index] = true;
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (result === false) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
this.close();
|
|
127
|
-
},
|
|
128
|
-
open() {
|
|
129
|
-
this.visible = true;
|
|
130
|
-
},
|
|
131
|
-
close() {
|
|
132
|
-
this.visible = false;
|
|
133
|
-
},
|
|
134
|
-
onHidden() {
|
|
135
|
-
this.$emit('close');
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
</script>
|
|
140
|
-
|
|
141
|
-
<style>
|
|
142
|
-
.tnxbsv-dialog {
|
|
143
|
-
height: fit-content;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<BModal
|
|
3
|
+
dialog-class="tnxbsv-dialog"
|
|
4
|
+
header-class="tnxbsv-dialog-header"
|
|
5
|
+
title-class="tnxbsv-dialog-title"
|
|
6
|
+
body-class="tnxbsv-dialog-body"
|
|
7
|
+
:footer-class="buttons?.length ? 'tnxbsv-dialog-footer' : 'd-none'"
|
|
8
|
+
:data-v-id="id"
|
|
9
|
+
v-model="visible"
|
|
10
|
+
scrollable
|
|
11
|
+
no-close-on-backdrop
|
|
12
|
+
no-close-on-esc
|
|
13
|
+
:teleport-to="container"
|
|
14
|
+
@hidden="onHidden"
|
|
15
|
+
>
|
|
16
|
+
<template #title>
|
|
17
|
+
<div v-html="title" v-if="title"></div>
|
|
18
|
+
</template>
|
|
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
|
+
<template #footer>
|
|
22
|
+
<TnxbsvButton v-for="(button, index) in buttons" :key="index"
|
|
23
|
+
:type="button.type"
|
|
24
|
+
:loading="buttonLoadings[index]"
|
|
25
|
+
@click="btnClick(index)"
|
|
26
|
+
>
|
|
27
|
+
{{ button.caption || button.text }}
|
|
28
|
+
</TnxbsvButton>
|
|
29
|
+
</template>
|
|
30
|
+
</BModal>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
import {BModal} from 'bootstrap-vue-next';
|
|
35
|
+
import TnxbsvButton from '../button/Button.vue'
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
name: 'TnxbsvDialog',
|
|
39
|
+
components: {BModal, TnxbsvButton,},
|
|
40
|
+
props: {
|
|
41
|
+
id: {
|
|
42
|
+
type: [String, Number],
|
|
43
|
+
default: () => window.tnx.util.string.uuid32(),
|
|
44
|
+
},
|
|
45
|
+
modelValue: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
container: String,
|
|
50
|
+
title: String,
|
|
51
|
+
content: [String, Object],
|
|
52
|
+
contentProps: {
|
|
53
|
+
type: Object,
|
|
54
|
+
default: () => ({}),
|
|
55
|
+
},
|
|
56
|
+
buttons: Array,
|
|
57
|
+
theme: String,
|
|
58
|
+
width: {
|
|
59
|
+
type: [Number, String],
|
|
60
|
+
default: 512,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
emits: ['update:modelValue', 'close'],
|
|
64
|
+
data() {
|
|
65
|
+
return {
|
|
66
|
+
visible: this.modelValue,
|
|
67
|
+
buttonLoadings: [],
|
|
68
|
+
heightChangeObserver: null,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
watch: {
|
|
72
|
+
modelValue(newValue, oldValue) {
|
|
73
|
+
this.visible = this.modelValue;
|
|
74
|
+
if (newValue && !oldValue) { // 从隐藏到显示
|
|
75
|
+
this.$nextTick(() => {
|
|
76
|
+
this.locate(true);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
visible() {
|
|
81
|
+
this.$emit('update:modelValue', this.visible);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
mounted() {
|
|
85
|
+
window.tnx.app.eventBus.once('tnx.error', options => {
|
|
86
|
+
this.buttonLoadings = [];
|
|
87
|
+
});
|
|
88
|
+
this.$nextTick(() => {
|
|
89
|
+
if (this.visible) {
|
|
90
|
+
this.locate(true);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
beforeUnmount() {
|
|
95
|
+
window.tnx.app.eventBus.off('tnx.error');
|
|
96
|
+
},
|
|
97
|
+
methods: {
|
|
98
|
+
locate(observe) {
|
|
99
|
+
const $ = window.tnx.libs.$;
|
|
100
|
+
let $dialog = $(`${this.container} .tnxbsv-dialog`);
|
|
101
|
+
if ($dialog.length) {
|
|
102
|
+
let top = window.tnx.util.dom.getTopVerticallyCenteredOnPage($dialog[0]);
|
|
103
|
+
let width = typeof this.width === 'number' ? this.width + 'px' : this.width;
|
|
104
|
+
$dialog.css({
|
|
105
|
+
'margin-top': top + 'px',
|
|
106
|
+
'width': width,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (observe) {
|
|
110
|
+
this.heightChangeObserver = window.tnx.util.dom.observeHeightChange($dialog[0], this.locate);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
btnClick(index) {
|
|
115
|
+
const button = this.buttons[index];
|
|
116
|
+
if (button && typeof button.click === 'function') {
|
|
117
|
+
let result = button.click.call(this.$refs.content, this.close);
|
|
118
|
+
if (result === 'loading') {
|
|
119
|
+
this.buttonLoadings[index] = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (result === false) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
this.close();
|
|
127
|
+
},
|
|
128
|
+
open() {
|
|
129
|
+
this.visible = true;
|
|
130
|
+
},
|
|
131
|
+
close() {
|
|
132
|
+
this.visible = false;
|
|
133
|
+
},
|
|
134
|
+
onHidden() {
|
|
135
|
+
this.$emit('close');
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<style>
|
|
142
|
+
.tnxbsv-dialog {
|
|
143
|
+
height: fit-content;
|
|
144
|
+
margin-bottom: 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.tnxbsv-dialog.modal-dialog-scrollable .modal-content {
|
|
148
|
+
max-height: calc(100vh - 1rem);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.tnxbsv-dialog .modal-header {
|
|
152
|
+
padding: 0.75rem 1rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.tnxbsv-dialog .modal-header .tnxbsv-dialog-title {
|
|
156
|
+
font-size: 1.1rem;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tnxbsv-dialog .modal-header .btn-close {
|
|
160
|
+
--bs-btn-close-opacity: 0.25;
|
|
161
|
+
--bs-btn-close-hover-opacity: 0.5;
|
|
162
|
+
}
|
|
163
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<BForm :id="id" class="tnxbsv-form">
|
|
2
|
+
<BForm :id="id" class="tnxbsv-form" :class="{initializing: initializing}">
|
|
3
3
|
<slot></slot>
|
|
4
4
|
</BForm>
|
|
5
5
|
</template>
|
|
@@ -24,9 +24,21 @@ 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
|
+
initializing: true,
|
|
32
|
+
fieldEventListeners: {},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
watch: {
|
|
36
|
+
rules() {
|
|
37
|
+
this.initRules();
|
|
38
|
+
},
|
|
39
|
+
disabled() {
|
|
40
|
+
this.updateElementsDisabled();
|
|
41
|
+
},
|
|
30
42
|
},
|
|
31
43
|
mounted() {
|
|
32
44
|
window.addEventListener('resize', this.updateLabelWidth);
|
|
@@ -34,21 +46,22 @@ export default {
|
|
|
34
46
|
setTimeout(() => {
|
|
35
47
|
this.initRules();
|
|
36
48
|
this.updateLabelWidth();
|
|
49
|
+
this.updateElementsDisabled();
|
|
50
|
+
this.initializing = false;
|
|
37
51
|
});
|
|
38
52
|
});
|
|
39
53
|
},
|
|
40
54
|
beforeUnmount() {
|
|
41
55
|
window.removeEventListener('resize', this.updateLabelWidth);
|
|
56
|
+
this.clearEventListeners();
|
|
42
57
|
},
|
|
43
58
|
methods: {
|
|
44
59
|
getFieldGroupElements() {
|
|
45
60
|
let elements = {};
|
|
46
|
-
let groups = this.$el.querySelectorAll(
|
|
47
|
-
groups.forEach(
|
|
48
|
-
let fieldName =
|
|
49
|
-
|
|
50
|
-
elements[fieldName] = label;
|
|
51
|
-
}
|
|
61
|
+
let groups = this.$el.querySelectorAll('.b-form-group[prop]');
|
|
62
|
+
groups.forEach(group => {
|
|
63
|
+
let fieldName = group.getAttribute('prop');
|
|
64
|
+
elements[fieldName] = group;
|
|
52
65
|
});
|
|
53
66
|
return elements;
|
|
54
67
|
},
|
|
@@ -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
|
}
|
|
@@ -93,16 +133,13 @@ export default {
|
|
|
93
133
|
},
|
|
94
134
|
updateLabelWidth() {
|
|
95
135
|
let maxWidth = 0;
|
|
96
|
-
let
|
|
97
|
-
for (let
|
|
98
|
-
let
|
|
99
|
-
if (
|
|
100
|
-
let
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
if (width > maxWidth) {
|
|
104
|
-
maxWidth = width;
|
|
105
|
-
}
|
|
136
|
+
let groupElements = this.$el.querySelectorAll('.b-form-group');
|
|
137
|
+
for (let groupElement of groupElements) {
|
|
138
|
+
let label = groupElement.querySelector('.form-label');
|
|
139
|
+
if (label) {
|
|
140
|
+
let width = label.offsetWidth;
|
|
141
|
+
if (width > maxWidth) {
|
|
142
|
+
maxWidth = width;
|
|
106
143
|
}
|
|
107
144
|
}
|
|
108
145
|
}
|
|
@@ -112,98 +149,123 @@ export default {
|
|
|
112
149
|
},
|
|
113
150
|
validate() {
|
|
114
151
|
this.clearValidate(); // 清除之前的反馈信息
|
|
115
|
-
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
116
152
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
let errors = {};
|
|
155
|
+
let fieldGroupElements = this.getFieldGroupElements();
|
|
156
|
+
for (let fieldName in fieldGroupElements) {
|
|
157
|
+
let fieldErrorMessages = this.validateField(fieldName);
|
|
158
|
+
if (fieldErrorMessages.length) {
|
|
159
|
+
errors[fieldName] = fieldErrorMessages;
|
|
160
|
+
}
|
|
122
161
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
if (Object.keys(errors).length) {
|
|
163
|
+
reject(errors);
|
|
164
|
+
}
|
|
165
|
+
resolve();
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
getFieldGroupElement(fieldName) {
|
|
169
|
+
return this.$el.querySelector(`.b-form-group[prop="${fieldName}"]`);
|
|
170
|
+
},
|
|
171
|
+
queryFieldElement(fieldGroupElement) {
|
|
172
|
+
return fieldGroupElement ? fieldGroupElement.querySelector('input, textarea, select') : null;
|
|
130
173
|
},
|
|
131
174
|
getFieldElement(fieldName) {
|
|
132
|
-
let
|
|
133
|
-
|
|
134
|
-
return groupElement.querySelector('input, textarea, select');
|
|
135
|
-
}
|
|
136
|
-
return undefined;
|
|
175
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
176
|
+
return this.queryFieldElement(fieldGroupElement);
|
|
137
177
|
},
|
|
138
178
|
validateField(fieldName, trigger) {
|
|
139
179
|
const fieldValue = this.model[fieldName];
|
|
140
180
|
const fieldRules = this.getFieldRules(fieldName);
|
|
141
|
-
const
|
|
181
|
+
const fieldErrorMessages = [];
|
|
142
182
|
|
|
143
183
|
fieldRules.forEach(fieldRule => {
|
|
144
184
|
if (typeof fieldRule.validator === 'function' && (!trigger || trigger === fieldRule.trigger)) {
|
|
145
185
|
fieldRule.validator(fieldRule, fieldValue, (error) => {
|
|
146
186
|
if (error) {
|
|
147
|
-
|
|
187
|
+
fieldErrorMessages.push(error.message);
|
|
148
188
|
}
|
|
149
189
|
});
|
|
150
190
|
}
|
|
151
191
|
// 没有validator()方法,则不进行校验
|
|
152
192
|
});
|
|
153
193
|
|
|
154
|
-
if (
|
|
155
|
-
this.setFieldInvalidFeedback(fieldName,
|
|
156
|
-
return false;
|
|
194
|
+
if (fieldErrorMessages.length) {
|
|
195
|
+
this.setFieldInvalidFeedback(fieldName, fieldErrorMessages);
|
|
157
196
|
} else {
|
|
158
197
|
// 清除错误状态
|
|
159
198
|
this.removeFieldInvalidFeedback(fieldName);
|
|
160
|
-
return true;
|
|
161
199
|
}
|
|
200
|
+
return fieldErrorMessages;
|
|
162
201
|
},
|
|
163
202
|
setFieldInvalidFeedback(fieldName, messages) {
|
|
164
|
-
let
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// 如果下一个兄弟元素不是 .invalid-feedback,则创建一个新的
|
|
169
|
-
if (!feedbackDiv || !feedbackDiv.classList.contains('invalid-feedback')) {
|
|
203
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
204
|
+
if (fieldGroupElement) {
|
|
205
|
+
let feedbackDiv = fieldGroupElement.querySelector('.invalid-feedback');
|
|
206
|
+
if (!feedbackDiv) {
|
|
170
207
|
feedbackDiv = document.createElement('div');
|
|
171
208
|
feedbackDiv.className = 'invalid-feedback';
|
|
172
|
-
|
|
209
|
+
let contentWrapper = fieldGroupElement.querySelector('.tnxbsv-form-group__content-wrapper');
|
|
210
|
+
// 有分组元素的情况下,一定有contentWrapper
|
|
211
|
+
contentWrapper.appendChild(feedbackDiv);
|
|
173
212
|
}
|
|
174
213
|
// 更新 .invalid-feedback 的内容
|
|
175
214
|
feedbackDiv.textContent = messages.join('; ');
|
|
176
215
|
// 设置输入框为无效状态
|
|
177
|
-
fieldElement.
|
|
216
|
+
let fieldElement = this.queryFieldElement(fieldGroupElement);
|
|
217
|
+
if (fieldElement) {
|
|
218
|
+
fieldElement.classList.add('is-invalid');
|
|
219
|
+
}
|
|
178
220
|
}
|
|
179
221
|
},
|
|
180
222
|
removeFieldInvalidFeedback(fieldName) {
|
|
181
|
-
let
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// 如果存在 .invalid-feedback 元素,则移除它
|
|
186
|
-
if (feedbackDiv && feedbackDiv.classList.contains('invalid-feedback')) {
|
|
223
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
224
|
+
if (fieldGroupElement) {
|
|
225
|
+
let feedbackDiv = fieldGroupElement.querySelector('.invalid-feedback');
|
|
226
|
+
if (feedbackDiv) {
|
|
187
227
|
feedbackDiv.remove();
|
|
188
228
|
}
|
|
189
229
|
// 移除无效状态
|
|
190
|
-
fieldElement.
|
|
230
|
+
let fieldElement = this.queryFieldElement(fieldGroupElement);
|
|
231
|
+
if (fieldElement) {
|
|
232
|
+
fieldElement.classList.remove('is-invalid');
|
|
233
|
+
}
|
|
191
234
|
}
|
|
192
235
|
},
|
|
193
236
|
clearValidate() {
|
|
194
|
-
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
195
|
-
|
|
196
237
|
const invalidElements = this.$el.querySelectorAll('.is-invalid');
|
|
197
238
|
invalidElements.forEach(el => el.classList.remove('is-invalid'));
|
|
198
239
|
|
|
199
240
|
const invalidFeedbackElements = this.$el.querySelectorAll('.invalid-feedback');
|
|
200
241
|
invalidFeedbackElements.forEach(el => el.remove());
|
|
201
242
|
},
|
|
243
|
+
scrollToField(fieldName) {
|
|
244
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
245
|
+
if (fieldGroupElement) {
|
|
246
|
+
let fieldElement = this.queryFieldElement(fieldGroupElement);
|
|
247
|
+
if (fieldElement) {
|
|
248
|
+
fieldElement.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
249
|
+
} else {
|
|
250
|
+
fieldGroupElement.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
updateElementsDisabled() {
|
|
255
|
+
let elements = this.$el.querySelectorAll('input, textarea, select, button');
|
|
256
|
+
elements.forEach(element => {
|
|
257
|
+
element.disabled = this.disabled;
|
|
258
|
+
});
|
|
259
|
+
},
|
|
202
260
|
}
|
|
203
261
|
}
|
|
204
262
|
</script>
|
|
205
263
|
|
|
206
264
|
<style>
|
|
265
|
+
.tnxbsv-form.initializing .form-label {
|
|
266
|
+
visibility: hidden;
|
|
267
|
+
}
|
|
268
|
+
|
|
207
269
|
.tnxbsv-form .b-form-group {
|
|
208
270
|
display: flex;
|
|
209
271
|
}
|
|
@@ -227,6 +289,7 @@ export default {
|
|
|
227
289
|
}
|
|
228
290
|
|
|
229
291
|
.tnxbsv-form .b-form-group .invalid-feedback {
|
|
292
|
+
display: block;
|
|
230
293
|
}
|
|
231
294
|
|
|
232
295
|
@media (max-width: var(--bs-breakpoint-md)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<span class="spinner-border" :class="extraClass" role="status">
|
|
2
|
+
<span class="tnxbsv-loading-icon spinner-border" :class="extraClass" role="status">
|
|
3
3
|
<span class="visually-hidden">{{ text }}</span>
|
|
4
4
|
</span>
|
|
5
5
|
</template>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<script>
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
|
-
name: '
|
|
10
|
+
name: 'TnxbsvLoadingIcon',
|
|
11
11
|
props: {
|
|
12
12
|
small: {
|
|
13
13
|
type: Boolean,
|