@truenewx/tnxvue3 3.0.5 → 3.0.7
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 +3 -3
- package/src/bootstrap-vue/cascader/Cascader.vue +491 -0
- package/src/bootstrap-vue/dialog/Dialog.vue +164 -158
- package/src/bootstrap-vue/enum-select/EnumSelect.vue +1 -3
- package/src/bootstrap-vue/form/Form.vue +56 -43
- package/src/bootstrap-vue/form/FormGroup.vue +26 -1
- 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/query-form/QueryForm.vue +41 -0
- package/src/bootstrap-vue/region-cascader/RegionCascader.vue +119 -0
- package/src/bootstrap-vue/select/Select.vue +32 -14
- package/src/bootstrap-vue/tags-input/TagsInput.vue +64 -0
- package/src/bootstrap-vue/tnxbsv.css +47 -7
- package/src/bootstrap-vue/tnxbsv.js +123 -2
- package/src/element-plus/tnxel.js +2 -3
- package/src/tnxvue.js +2 -2
|
@@ -1,158 +1,164 @@
|
|
|
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
|
-
|
|
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
|
+
max-width: 100%;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.tnxbsv-dialog.modal-dialog-scrollable .modal-content {
|
|
149
|
+
max-height: calc(100vh - 1rem);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.tnxbsv-dialog .modal-header {
|
|
153
|
+
padding: 0.75rem 1rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.tnxbsv-dialog .modal-header .tnxbsv-dialog-title {
|
|
157
|
+
font-size: 1.1rem;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.tnxbsv-dialog .modal-header .btn-close {
|
|
161
|
+
--bs-btn-close-opacity: 0.25;
|
|
162
|
+
--bs-btn-close-hover-opacity: 0.5;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
:default-value="defaultValue"
|
|
8
8
|
:empty="empty"
|
|
9
9
|
:empty-value="emptyValue"
|
|
10
|
-
:placeholder="placeholder"
|
|
11
10
|
:disabled="disabled"
|
|
12
11
|
:filterable="filterable"
|
|
13
12
|
:theme="theme"
|
|
@@ -39,9 +38,8 @@ export default {
|
|
|
39
38
|
},
|
|
40
39
|
emptyValue: {
|
|
41
40
|
type: [String, Number, Boolean, Array],
|
|
42
|
-
default:
|
|
41
|
+
default: null,
|
|
43
42
|
},
|
|
44
|
-
placeholder: String,
|
|
45
43
|
disabled: Boolean,
|
|
46
44
|
tagClick: Function,
|
|
47
45
|
change: Function,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<BForm :id="id" class="tnxbsv-form">
|
|
2
|
+
<BForm :id="id" class="tnxbsv-form" :class="{initializing: initializing, 'form-inline': inline}">
|
|
3
3
|
<slot></slot>
|
|
4
4
|
</BForm>
|
|
5
5
|
</template>
|
|
@@ -28,6 +28,7 @@ export default {
|
|
|
28
28
|
},
|
|
29
29
|
data() {
|
|
30
30
|
return {
|
|
31
|
+
initializing: true,
|
|
31
32
|
fieldEventListeners: {},
|
|
32
33
|
};
|
|
33
34
|
},
|
|
@@ -46,6 +47,7 @@ export default {
|
|
|
46
47
|
this.initRules();
|
|
47
48
|
this.updateLabelWidth();
|
|
48
49
|
this.updateElementsDisabled();
|
|
50
|
+
this.initializing = false;
|
|
49
51
|
});
|
|
50
52
|
});
|
|
51
53
|
},
|
|
@@ -57,11 +59,9 @@ export default {
|
|
|
57
59
|
getFieldGroupElements() {
|
|
58
60
|
let elements = {};
|
|
59
61
|
let groups = this.$el.querySelectorAll('.b-form-group[prop]');
|
|
60
|
-
groups.forEach(
|
|
61
|
-
let fieldName =
|
|
62
|
-
|
|
63
|
-
elements[fieldName] = label;
|
|
64
|
-
}
|
|
62
|
+
groups.forEach(group => {
|
|
63
|
+
let fieldName = group.getAttribute('prop');
|
|
64
|
+
elements[fieldName] = group;
|
|
65
65
|
});
|
|
66
66
|
return elements;
|
|
67
67
|
},
|
|
@@ -132,17 +132,17 @@ export default {
|
|
|
132
132
|
}
|
|
133
133
|
},
|
|
134
134
|
updateLabelWidth() {
|
|
135
|
+
if (this.inline) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
135
138
|
let maxWidth = 0;
|
|
136
|
-
let
|
|
137
|
-
for (let
|
|
138
|
-
let
|
|
139
|
-
if (
|
|
140
|
-
let
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
if (width > maxWidth) {
|
|
144
|
-
maxWidth = width;
|
|
145
|
-
}
|
|
139
|
+
let groupElements = this.$el.querySelectorAll('.b-form-group');
|
|
140
|
+
for (let groupElement of groupElements) {
|
|
141
|
+
let label = groupElement.querySelector('.form-label');
|
|
142
|
+
if (label) {
|
|
143
|
+
let width = label.offsetWidth;
|
|
144
|
+
if (width > maxWidth) {
|
|
145
|
+
maxWidth = width;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
}
|
|
@@ -152,7 +152,6 @@ export default {
|
|
|
152
152
|
},
|
|
153
153
|
validate() {
|
|
154
154
|
this.clearValidate(); // 清除之前的反馈信息
|
|
155
|
-
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
156
155
|
|
|
157
156
|
return new Promise((resolve, reject) => {
|
|
158
157
|
let errors = {};
|
|
@@ -164,18 +163,20 @@ export default {
|
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
if (Object.keys(errors).length) {
|
|
167
|
-
this.$el.classList.add('was-validated'); // 添加已验证样式
|
|
168
166
|
reject(errors);
|
|
169
167
|
}
|
|
170
168
|
resolve();
|
|
171
169
|
});
|
|
172
170
|
},
|
|
171
|
+
getFieldGroupElement(fieldName) {
|
|
172
|
+
return this.$el.querySelector(`.b-form-group[prop="${fieldName}"]`);
|
|
173
|
+
},
|
|
174
|
+
queryFieldElement(fieldGroupElement) {
|
|
175
|
+
return fieldGroupElement ? fieldGroupElement.querySelector('input, textarea, select') : null;
|
|
176
|
+
},
|
|
173
177
|
getFieldElement(fieldName) {
|
|
174
|
-
let
|
|
175
|
-
|
|
176
|
-
return groupElement.querySelector('input, textarea, select');
|
|
177
|
-
}
|
|
178
|
-
return undefined;
|
|
178
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
179
|
+
return this.queryFieldElement(fieldGroupElement);
|
|
179
180
|
},
|
|
180
181
|
validateField(fieldName, trigger) {
|
|
181
182
|
const fieldValue = this.model[fieldName];
|
|
@@ -202,38 +203,40 @@ export default {
|
|
|
202
203
|
return fieldErrorMessages;
|
|
203
204
|
},
|
|
204
205
|
setFieldInvalidFeedback(fieldName, messages) {
|
|
205
|
-
let
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// 如果下一个兄弟元素不是 .invalid-feedback,则创建一个新的
|
|
210
|
-
if (!feedbackDiv || !feedbackDiv.classList.contains('invalid-feedback')) {
|
|
206
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
207
|
+
if (fieldGroupElement) {
|
|
208
|
+
let feedbackDiv = fieldGroupElement.querySelector('.invalid-feedback');
|
|
209
|
+
if (!feedbackDiv) {
|
|
211
210
|
feedbackDiv = document.createElement('div');
|
|
212
211
|
feedbackDiv.className = 'invalid-feedback';
|
|
213
|
-
|
|
212
|
+
let contentWrapper = fieldGroupElement.querySelector('.tnxbsv-form-group__content-wrapper');
|
|
213
|
+
// 有分组元素的情况下,一定有contentWrapper
|
|
214
|
+
contentWrapper.appendChild(feedbackDiv);
|
|
214
215
|
}
|
|
215
216
|
// 更新 .invalid-feedback 的内容
|
|
216
217
|
feedbackDiv.textContent = messages.join('; ');
|
|
217
218
|
// 设置输入框为无效状态
|
|
218
|
-
fieldElement.
|
|
219
|
+
let fieldElement = this.queryFieldElement(fieldGroupElement);
|
|
220
|
+
if (fieldElement) {
|
|
221
|
+
fieldElement.classList.add('is-invalid');
|
|
222
|
+
}
|
|
219
223
|
}
|
|
220
224
|
},
|
|
221
225
|
removeFieldInvalidFeedback(fieldName) {
|
|
222
|
-
let
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// 如果存在 .invalid-feedback 元素,则移除它
|
|
227
|
-
if (feedbackDiv && feedbackDiv.classList.contains('invalid-feedback')) {
|
|
226
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
227
|
+
if (fieldGroupElement) {
|
|
228
|
+
let feedbackDiv = fieldGroupElement.querySelector('.invalid-feedback');
|
|
229
|
+
if (feedbackDiv) {
|
|
228
230
|
feedbackDiv.remove();
|
|
229
231
|
}
|
|
230
232
|
// 移除无效状态
|
|
231
|
-
fieldElement.
|
|
233
|
+
let fieldElement = this.queryFieldElement(fieldGroupElement);
|
|
234
|
+
if (fieldElement) {
|
|
235
|
+
fieldElement.classList.remove('is-invalid');
|
|
236
|
+
}
|
|
232
237
|
}
|
|
233
238
|
},
|
|
234
239
|
clearValidate() {
|
|
235
|
-
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
236
|
-
|
|
237
240
|
const invalidElements = this.$el.querySelectorAll('.is-invalid');
|
|
238
241
|
invalidElements.forEach(el => el.classList.remove('is-invalid'));
|
|
239
242
|
|
|
@@ -241,9 +244,14 @@ export default {
|
|
|
241
244
|
invalidFeedbackElements.forEach(el => el.remove());
|
|
242
245
|
},
|
|
243
246
|
scrollToField(fieldName) {
|
|
244
|
-
let
|
|
245
|
-
if (
|
|
246
|
-
fieldElement.
|
|
247
|
+
let fieldGroupElement = this.getFieldGroupElement(fieldName);
|
|
248
|
+
if (fieldGroupElement) {
|
|
249
|
+
let fieldElement = this.queryFieldElement(fieldGroupElement);
|
|
250
|
+
if (fieldElement) {
|
|
251
|
+
fieldElement.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
252
|
+
} else {
|
|
253
|
+
fieldGroupElement.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
254
|
+
}
|
|
247
255
|
}
|
|
248
256
|
},
|
|
249
257
|
updateElementsDisabled() {
|
|
@@ -257,6 +265,10 @@ export default {
|
|
|
257
265
|
</script>
|
|
258
266
|
|
|
259
267
|
<style>
|
|
268
|
+
.tnxbsv-form.initializing .form-label {
|
|
269
|
+
visibility: hidden;
|
|
270
|
+
}
|
|
271
|
+
|
|
260
272
|
.tnxbsv-form .b-form-group {
|
|
261
273
|
display: flex;
|
|
262
274
|
}
|
|
@@ -280,6 +292,7 @@ export default {
|
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
.tnxbsv-form .b-form-group .invalid-feedback {
|
|
295
|
+
display: block;
|
|
283
296
|
}
|
|
284
297
|
|
|
285
298
|
@media (max-width: var(--bs-breakpoint-md)) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="tnxbsv-form-group b-form-group" :prop="prop">
|
|
3
3
|
<div class="tnxbsv-form-group__label-wrapper">
|
|
4
4
|
<slot name="label">
|
|
5
|
-
<label class="form-label">{{ label }}</label>
|
|
5
|
+
<label class="form-label" :style="labelStyle">{{ label }}</label>
|
|
6
6
|
</slot>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="tnxbsv-form-group__content-wrapper">
|
|
@@ -16,6 +16,7 @@ export default {
|
|
|
16
16
|
name: 'TnxbsvFormGroup',
|
|
17
17
|
props: {
|
|
18
18
|
label: String,
|
|
19
|
+
labelWidth: [String, Number],
|
|
19
20
|
prop: String,
|
|
20
21
|
},
|
|
21
22
|
data() {
|
|
@@ -23,6 +24,26 @@ export default {
|
|
|
23
24
|
model: {},
|
|
24
25
|
};
|
|
25
26
|
},
|
|
27
|
+
computed: {
|
|
28
|
+
labelStyle() {
|
|
29
|
+
let style = {};
|
|
30
|
+
if (this.labelWidth) {
|
|
31
|
+
let width;
|
|
32
|
+
if (typeof this.labelWidth === 'string') {
|
|
33
|
+
// 如果是纯数字内容,则附加px单位
|
|
34
|
+
if (/^\d+$/.test(this.labelWidth)) {
|
|
35
|
+
width = this.labelWidth + 'px';
|
|
36
|
+
} else {
|
|
37
|
+
width = this.labelWidth;
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
width = this.labelWidth + 'px';
|
|
41
|
+
}
|
|
42
|
+
style.width = width;
|
|
43
|
+
}
|
|
44
|
+
return style;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
26
47
|
methods: {}
|
|
27
48
|
}
|
|
28
49
|
</script>
|
|
@@ -38,5 +59,9 @@ export default {
|
|
|
38
59
|
|
|
39
60
|
.tnxbsv-form-group__content-wrapper {
|
|
40
61
|
flex-grow: 1;
|
|
62
|
+
min-height: 2.375rem;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
justify-content: center;
|
|
41
66
|
}
|
|
42
67
|
</style>
|
|
@@ -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,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="loading-overlay">
|
|
3
|
+
<LoadingIcon theme="primary" :small="false" :text="message"/>
|
|
4
|
+
<span class="loading-message text-primary" v-if="message">{{ message }}</span>
|
|
5
|
+
</div>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script>
|
|
9
|
+
import LoadingIcon from "../loading-icon/LoadingIcon.vue";
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
name: 'TnxbsvLoadingMask',
|
|
13
|
+
components: {LoadingIcon},
|
|
14
|
+
props: {
|
|
15
|
+
message: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: '',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
data() {
|
|
21
|
+
return {};
|
|
22
|
+
},
|
|
23
|
+
mounted() {
|
|
24
|
+
document.body.classList.add('no-scroll')
|
|
25
|
+
},
|
|
26
|
+
beforeUnmount() {
|
|
27
|
+
document.body.classList.remove('no-scroll');
|
|
28
|
+
},
|
|
29
|
+
methods: {}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
.no-scroll {
|
|
35
|
+
overflow: hidden !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.loading-overlay {
|
|
39
|
+
position: fixed;
|
|
40
|
+
top: 0;
|
|
41
|
+
left: 0;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: 100%;
|
|
44
|
+
background-color: rgba(var(--bs-white-rgb), 0.8);
|
|
45
|
+
display: flex;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
align-items: center;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
z-index: 2147483647; /* 浏览器支持的最大值 */
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.loading-overlay .spinner-border {
|
|
53
|
+
opacity: 0.7;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.loading-overlay .loading-message {
|
|
57
|
+
margin-top: 0.25rem;
|
|
58
|
+
opacity: 0.7;
|
|
59
|
+
}
|
|
60
|
+
</style>
|