@truenewx/tnxvue3 3.0.2 → 3.0.4
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 +18 -5
- package/src/bootstrap-vue/dialog/Dialog.vue +158 -0
- package/src/bootstrap-vue/form/Form.vue +243 -0
- package/src/bootstrap-vue/form/FormGroup.vue +42 -0
- package/src/bootstrap-vue/loading/Loading.vue +5 -2
- package/src/bootstrap-vue/{pagination/Pagination.vue → paged/Paged.vue} +36 -19
- package/src/bootstrap-vue/query-table/QueryTable.vue +3 -3
- package/src/bootstrap-vue/tnxbsv.css +5 -0
- package/src/bootstrap-vue/tnxbsv.js +32 -6
- package/src/element-plus/dialog/Dialog.vue +4 -6
- package/src/element-plus/paged/Paged.vue +4 -4
- package/src/tnxvue.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truenewx/tnxvue3",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "互联网技术解决方案:Vue3扩展支持",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"vue-router": "~4.4.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@truenewx/tnxcore": "3.0.
|
|
28
|
+
"@truenewx/tnxcore": "3.0.3",
|
|
29
29
|
"@element-plus/icons-vue": "2.3.1",
|
|
30
30
|
"async-validator": "4.2.5",
|
|
31
31
|
"mitt": "3.0.1"
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<BButton>
|
|
3
|
-
<i :class="icon" v-if="icon"></i>
|
|
4
|
-
<
|
|
2
|
+
<BButton class="tnxbsv-button" :loading="loading" :disabled="loading">
|
|
3
|
+
<i class="me-1" :class="icon" v-if="icon"></i>
|
|
4
|
+
<div>
|
|
5
|
+
<slot></slot>
|
|
6
|
+
</div>
|
|
7
|
+
<template #loading>
|
|
8
|
+
<Loading class="me-1" theme="inherit"/>
|
|
9
|
+
<div>
|
|
10
|
+
<slot></slot>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
5
13
|
</BButton>
|
|
6
14
|
</template>
|
|
7
15
|
|
|
8
16
|
<script>
|
|
9
17
|
import {BButton} from 'bootstrap-vue-next';
|
|
18
|
+
import Loading from '../loading/Loading.vue';
|
|
10
19
|
|
|
11
20
|
export default {
|
|
12
21
|
name: 'TnxbsvButton',
|
|
13
|
-
components: {BButton},
|
|
22
|
+
components: {BButton, Loading},
|
|
14
23
|
props: {
|
|
15
24
|
icon: String,
|
|
25
|
+
loading: Boolean,
|
|
16
26
|
},
|
|
17
27
|
data() {
|
|
18
28
|
return {};
|
|
@@ -22,5 +32,8 @@ export default {
|
|
|
22
32
|
</script>
|
|
23
33
|
|
|
24
34
|
<style>
|
|
25
|
-
|
|
35
|
+
.tnxbsv-button {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
}
|
|
26
39
|
</style>
|
|
@@ -0,0 +1,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 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
|
+
:variant="button.type || 'outline-secondary'"
|
|
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
|
+
.tnxbsv-dialog .modal-header {
|
|
147
|
+
padding: 0.75rem 1rem;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.tnxbsv-dialog .modal-header .tnxbsv-dialog-title {
|
|
151
|
+
font-size: 1.1rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.tnxbsv-dialog .modal-header .btn-close {
|
|
155
|
+
--bs-btn-close-opacity: 0.25;
|
|
156
|
+
--bs-btn-close-hover-opacity: 0.5;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BForm :id="id" class="tnxbsv-form">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</BForm>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import {BForm} from 'bootstrap-vue-next';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: 'TnxbsvForm',
|
|
12
|
+
components: {BForm},
|
|
13
|
+
props: {
|
|
14
|
+
id: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: () => 'tnxbsv-form-' + new Date().getTime(),
|
|
17
|
+
},
|
|
18
|
+
model: {
|
|
19
|
+
type: Object,
|
|
20
|
+
default: () => ({}),
|
|
21
|
+
},
|
|
22
|
+
inline: Boolean,
|
|
23
|
+
rules: {
|
|
24
|
+
type: Object, // key: 字段名,value: 验证规则数组
|
|
25
|
+
default: () => ({}),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
data() {
|
|
29
|
+
return {};
|
|
30
|
+
},
|
|
31
|
+
mounted() {
|
|
32
|
+
window.addEventListener('resize', this.updateLabelWidth);
|
|
33
|
+
this.$nextTick(() => {
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
this.initRules();
|
|
36
|
+
this.updateLabelWidth();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
beforeUnmount() {
|
|
41
|
+
window.removeEventListener('resize', this.updateLabelWidth);
|
|
42
|
+
},
|
|
43
|
+
methods: {
|
|
44
|
+
getFieldGroupElements() {
|
|
45
|
+
let elements = {};
|
|
46
|
+
let groups = this.$el.querySelectorAll(`#${this.id} .b-form-group[prop]`);
|
|
47
|
+
groups.forEach(label => {
|
|
48
|
+
let fieldName = label.getAttribute('prop');
|
|
49
|
+
if (fieldName) {
|
|
50
|
+
elements[fieldName] = label;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return elements;
|
|
54
|
+
},
|
|
55
|
+
getFieldRules(fieldName) {
|
|
56
|
+
let fieldRules = this.rules[fieldName] || [];
|
|
57
|
+
if (!Array.isArray(fieldRules)) {
|
|
58
|
+
fieldRules = [fieldRules];
|
|
59
|
+
}
|
|
60
|
+
return fieldRules;
|
|
61
|
+
},
|
|
62
|
+
initRules() {
|
|
63
|
+
let fieldGroupElements = this.getFieldGroupElements();
|
|
64
|
+
for (let fieldName in fieldGroupElements) {
|
|
65
|
+
let fieldGroupElement = fieldGroupElements[fieldName];
|
|
66
|
+
if (fieldGroupElement) {
|
|
67
|
+
let fieldRules = this.getFieldRules(fieldName);
|
|
68
|
+
if (fieldRules.some(rule => rule.required)) {
|
|
69
|
+
let label = fieldGroupElement.querySelector('.form-label');
|
|
70
|
+
if (label) {
|
|
71
|
+
label.classList.add('is-required');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let existsChangeValidator = fieldRules.some(fieldRule => fieldRule.trigger === 'change');
|
|
75
|
+
let existsBlurValidator = fieldRules.some(fieldRule => fieldRule.trigger !== 'change');
|
|
76
|
+
if (existsChangeValidator || existsBlurValidator) {
|
|
77
|
+
let fieldElement = fieldGroupElement.querySelector('input, select, textarea');
|
|
78
|
+
if (fieldElement) {
|
|
79
|
+
if (existsChangeValidator) {
|
|
80
|
+
fieldElement.addEventListener('change', () => {
|
|
81
|
+
this.validateField(fieldName, 'change');
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (existsBlurValidator) {
|
|
85
|
+
fieldElement.addEventListener('blur', () => {
|
|
86
|
+
this.validateField(fieldName, 'blur');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
updateLabelWidth() {
|
|
95
|
+
let maxWidth = 0;
|
|
96
|
+
let fieldGroupElements = this.getFieldGroupElements();
|
|
97
|
+
for (let fieldName in fieldGroupElements) {
|
|
98
|
+
let fieldGroupElement = fieldGroupElements[fieldName];
|
|
99
|
+
if (fieldGroupElement) {
|
|
100
|
+
let label = fieldGroupElement.querySelector('.form-label');
|
|
101
|
+
if (label) {
|
|
102
|
+
let width = label.offsetWidth;
|
|
103
|
+
if (width > maxWidth) {
|
|
104
|
+
maxWidth = width;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (maxWidth) {
|
|
110
|
+
this.$el.style.setProperty('--label-width', `${maxWidth}px`);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
validate() {
|
|
114
|
+
this.clearValidate(); // 清除之前的反馈信息
|
|
115
|
+
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
116
|
+
|
|
117
|
+
let valid = true;
|
|
118
|
+
let fieldGroupElements = this.getFieldGroupElements();
|
|
119
|
+
for (let fieldName in fieldGroupElements) {
|
|
120
|
+
if (!this.validateField(fieldName)) {
|
|
121
|
+
valid = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!valid) {
|
|
126
|
+
this.$el.classList.add('was-validated'); // 添加已验证样式
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return valid;
|
|
130
|
+
},
|
|
131
|
+
getFieldElement(fieldName) {
|
|
132
|
+
let groupElement = this.$el.querySelector(`#${this.id} .b-form-group[prop="${fieldName}"]`);
|
|
133
|
+
if (groupElement) {
|
|
134
|
+
return groupElement.querySelector('input, textarea, select');
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
},
|
|
138
|
+
validateField(fieldName, trigger) {
|
|
139
|
+
const fieldValue = this.model[fieldName];
|
|
140
|
+
const fieldRules = this.getFieldRules(fieldName);
|
|
141
|
+
const fieldErrors = [];
|
|
142
|
+
|
|
143
|
+
fieldRules.forEach(fieldRule => {
|
|
144
|
+
if (typeof fieldRule.validator === 'function' && (!trigger || trigger === fieldRule.trigger)) {
|
|
145
|
+
fieldRule.validator(fieldRule, fieldValue, (error) => {
|
|
146
|
+
if (error) {
|
|
147
|
+
fieldErrors.push(error.message);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// 没有validator()方法,则不进行校验
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (fieldErrors.length > 0) {
|
|
155
|
+
this.setFieldInvalidFeedback(fieldName, fieldErrors);
|
|
156
|
+
return false;
|
|
157
|
+
} else {
|
|
158
|
+
// 清除错误状态
|
|
159
|
+
this.removeFieldInvalidFeedback(fieldName);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
setFieldInvalidFeedback(fieldName, messages) {
|
|
164
|
+
let fieldElement = this.getFieldElement(fieldName);
|
|
165
|
+
if (fieldElement) {
|
|
166
|
+
// 查找已有的 .invalid-feedback 元素
|
|
167
|
+
let feedbackDiv = fieldElement.nextElementSibling;
|
|
168
|
+
// 如果下一个兄弟元素不是 .invalid-feedback,则创建一个新的
|
|
169
|
+
if (!feedbackDiv || !feedbackDiv.classList.contains('invalid-feedback')) {
|
|
170
|
+
feedbackDiv = document.createElement('div');
|
|
171
|
+
feedbackDiv.className = 'invalid-feedback';
|
|
172
|
+
fieldElement.parentNode.insertBefore(feedbackDiv, fieldElement.nextSibling);
|
|
173
|
+
}
|
|
174
|
+
// 更新 .invalid-feedback 的内容
|
|
175
|
+
feedbackDiv.textContent = messages.join('; ');
|
|
176
|
+
// 设置输入框为无效状态
|
|
177
|
+
fieldElement.classList.add('is-invalid');
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
removeFieldInvalidFeedback(fieldName) {
|
|
181
|
+
let fieldElement = this.getFieldElement(fieldName);
|
|
182
|
+
if (fieldElement) {
|
|
183
|
+
// 查找已有的 .invalid-feedback 元素
|
|
184
|
+
let feedbackDiv = fieldElement.nextElementSibling;
|
|
185
|
+
// 如果存在 .invalid-feedback 元素,则移除它
|
|
186
|
+
if (feedbackDiv && feedbackDiv.classList.contains('invalid-feedback')) {
|
|
187
|
+
feedbackDiv.remove();
|
|
188
|
+
}
|
|
189
|
+
// 移除无效状态
|
|
190
|
+
fieldElement.classList.remove('is-invalid');
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
clearValidate() {
|
|
194
|
+
this.$el.classList.remove('was-validated'); // 移除已验证样式
|
|
195
|
+
|
|
196
|
+
const invalidElements = this.$el.querySelectorAll('.is-invalid');
|
|
197
|
+
invalidElements.forEach(el => el.classList.remove('is-invalid'));
|
|
198
|
+
|
|
199
|
+
const invalidFeedbackElements = this.$el.querySelectorAll('.invalid-feedback');
|
|
200
|
+
invalidFeedbackElements.forEach(el => el.remove());
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<style>
|
|
207
|
+
.tnxbsv-form .b-form-group {
|
|
208
|
+
display: flex;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.tnxbsv-form .b-form-group:not(:last-child) {
|
|
212
|
+
margin-bottom: 1rem;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.tnxbsv-form .b-form-group .form-label {
|
|
216
|
+
white-space: nowrap;
|
|
217
|
+
min-width: var(--label-width);
|
|
218
|
+
text-align: right;
|
|
219
|
+
margin-right: 0.75rem;
|
|
220
|
+
margin-bottom: 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.tnxbsv-form .b-form-group .form-label.is-required::before {
|
|
224
|
+
content: '*';
|
|
225
|
+
color: var(--bs-danger);
|
|
226
|
+
margin-right: 0.25rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.tnxbsv-form .b-form-group .invalid-feedback {
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@media (max-width: var(--bs-breakpoint-md)) {
|
|
233
|
+
.tnxbsv-form .b-form-group {
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.tnxbsv-form .b-form-group .form-label {
|
|
238
|
+
text-align: left;
|
|
239
|
+
margin-bottom: 0.5rem;
|
|
240
|
+
min-width: auto;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="tnxbsv-form-group b-form-group" :prop="prop">
|
|
3
|
+
<div class="tnxbsv-form-group__label-wrapper">
|
|
4
|
+
<slot name="label">
|
|
5
|
+
<label class="form-label">{{ label }}</label>
|
|
6
|
+
</slot>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="tnxbsv-form-group__content-wrapper">
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
export default {
|
|
16
|
+
name: 'TnxbsvFormGroup',
|
|
17
|
+
props: {
|
|
18
|
+
label: String,
|
|
19
|
+
prop: String,
|
|
20
|
+
},
|
|
21
|
+
data() {
|
|
22
|
+
return {
|
|
23
|
+
model: {},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
methods: {}
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
.tnxbsv-form-group {
|
|
32
|
+
display: flex;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tnxbsv-form-group__label-wrapper {
|
|
36
|
+
padding-top: 0.375rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.tnxbsv-form-group__content-wrapper {
|
|
40
|
+
flex-grow: 1;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -27,11 +27,14 @@ export default {
|
|
|
27
27
|
},
|
|
28
28
|
computed: {
|
|
29
29
|
extraClass() {
|
|
30
|
-
let extraClass = '
|
|
30
|
+
let extraClass = '';
|
|
31
|
+
if (this.theme && this.theme !== 'inherit') {
|
|
32
|
+
extraClass += ' text-' + this.theme;
|
|
33
|
+
}
|
|
31
34
|
if (this.small) {
|
|
32
35
|
extraClass += ' spinner-border-sm';
|
|
33
36
|
}
|
|
34
|
-
return extraClass;
|
|
37
|
+
return extraClass.trim();
|
|
35
38
|
},
|
|
36
39
|
},
|
|
37
40
|
methods: {}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tnxbsv-
|
|
3
|
-
<div class="
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
<div class="tnxbsv-paged" :class="'justify-content-' + align" v-if="value">
|
|
3
|
+
<div class="paged-text" v-if="showPageSize || showTotal">
|
|
4
|
+
<div class="page-size-wrapper" v-if="showPageSize">
|
|
5
|
+
<span>每页</span>
|
|
6
|
+
<Select v-model="pageSize" :items="pageSizeItems" v-if="pageSizeChangeable"/>
|
|
7
|
+
<span class="mx-1" v-else>{{ pageSize }}</span>
|
|
8
|
+
<span>条</span>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="total-wrapper" v-if="showTotal">
|
|
11
|
+
<span>共</span>
|
|
12
|
+
<span class="mx-1">{{ value.total }}</span>
|
|
13
|
+
<span>条</span>
|
|
14
|
+
</div>
|
|
10
15
|
</div>
|
|
11
16
|
<BPagination v-model="pageNo"
|
|
12
|
-
:total-rows="
|
|
13
|
-
:per-page="
|
|
17
|
+
:total-rows="value.total"
|
|
18
|
+
:per-page="value.pageSize"
|
|
14
19
|
:aria-controls="ariaControls"
|
|
15
20
|
:aria-label="ariaLabel"
|
|
16
21
|
/>
|
|
@@ -22,11 +27,19 @@ import {BPagination} from 'bootstrap-vue-next';
|
|
|
22
27
|
import Select from '../select/Select.vue';
|
|
23
28
|
|
|
24
29
|
export default {
|
|
25
|
-
name: '
|
|
30
|
+
name: 'TnxbsvPaged',
|
|
26
31
|
components: {BPagination, Select},
|
|
27
32
|
props: {
|
|
28
|
-
|
|
33
|
+
value: Object,
|
|
29
34
|
pageSizeChangeable: Boolean,
|
|
35
|
+
showPageSize: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: true,
|
|
38
|
+
},
|
|
39
|
+
showTotal: {
|
|
40
|
+
type: Boolean,
|
|
41
|
+
default: true,
|
|
42
|
+
},
|
|
30
43
|
align: {
|
|
31
44
|
type: String,
|
|
32
45
|
default: 'end', // start | center | end
|
|
@@ -37,8 +50,8 @@ export default {
|
|
|
37
50
|
},
|
|
38
51
|
data() {
|
|
39
52
|
return {
|
|
40
|
-
pageSize: this.
|
|
41
|
-
pageNo: this.
|
|
53
|
+
pageSize: this.value?.pageSize || 20,
|
|
54
|
+
pageNo: this.value?.pageNo || 1,
|
|
42
55
|
};
|
|
43
56
|
},
|
|
44
57
|
computed: {
|
|
@@ -77,26 +90,30 @@ export default {
|
|
|
77
90
|
</script>
|
|
78
91
|
|
|
79
92
|
<style>
|
|
80
|
-
.tnxbsv-
|
|
93
|
+
.tnxbsv-paged {
|
|
81
94
|
width: 100%;
|
|
82
95
|
display: flex;
|
|
83
96
|
align-items: center;
|
|
84
97
|
}
|
|
85
98
|
|
|
86
|
-
.tnxbsv-
|
|
99
|
+
.tnxbsv-paged .paged-text {
|
|
87
100
|
display: flex;
|
|
88
101
|
align-items: center;
|
|
89
102
|
color: var(--bs-secondary-color);
|
|
90
|
-
margin: 0 0.5rem;
|
|
91
103
|
white-space: nowrap;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
.tnxbsv-
|
|
106
|
+
.tnxbsv-paged .paged-text .page-size-wrapper,
|
|
107
|
+
.tnxbsv-paged .paged-text .total-wrapper {
|
|
108
|
+
margin-right: 0.5rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.tnxbsv-paged .form-select {
|
|
95
112
|
width: 4.5rem;
|
|
96
113
|
margin: 0 0.5rem;
|
|
97
114
|
}
|
|
98
115
|
|
|
99
|
-
.tnxbsv-
|
|
116
|
+
.tnxbsv-paged ul {
|
|
100
117
|
margin-bottom: 0;
|
|
101
118
|
}
|
|
102
119
|
</style>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<template #custom-foot>
|
|
12
12
|
<tr>
|
|
13
13
|
<td colspan="100%">
|
|
14
|
-
<
|
|
14
|
+
<Paged :value="result.paged"
|
|
15
15
|
:query="query"
|
|
16
16
|
:page-size-changeable="pageSizeChangeable"
|
|
17
17
|
:aria-controls="id"
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
<script>
|
|
26
26
|
import {BTable} from 'bootstrap-vue-next';
|
|
27
|
-
import
|
|
27
|
+
import Paged from '../paged/Paged.vue';
|
|
28
28
|
|
|
29
29
|
export default {
|
|
30
30
|
name: 'TnxbsvQueryTable',
|
|
31
|
-
components: {BTable,
|
|
31
|
+
components: {BTable, Paged},
|
|
32
32
|
props: {
|
|
33
33
|
id: {
|
|
34
34
|
type: String,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// tnxbsv.js
|
|
2
|
+
import tnxjq from '@truenewx/tnxcore/src/tnxjq';
|
|
2
3
|
import tnxvue from '../tnxvue.js';
|
|
3
4
|
import * as BootstrapVue from 'bootstrap-vue-next';
|
|
4
5
|
import 'bootstrap/dist/css/bootstrap.css';
|
|
@@ -6,9 +7,12 @@ import 'bootstrap-vue-next/dist/bootstrap-vue-next.css';
|
|
|
6
7
|
import './tnxbsv.css';
|
|
7
8
|
|
|
8
9
|
import Button from './button/Button.vue';
|
|
10
|
+
import Dialog from './dialog/Dialog.vue';
|
|
9
11
|
import EnumSelect from './enum-select/EnumSelect.vue';
|
|
12
|
+
import Form from './form/Form.vue';
|
|
13
|
+
import FormGroup from './form/FormGroup.vue';
|
|
10
14
|
import Loading from './loading/Loading.vue';
|
|
11
|
-
import
|
|
15
|
+
import Paged from './paged/Paged.vue';
|
|
12
16
|
import QueryTable from './query-table/QueryTable.vue';
|
|
13
17
|
import Select from './select/Select.vue';
|
|
14
18
|
|
|
@@ -16,20 +20,42 @@ export const build = tnxvue.build;
|
|
|
16
20
|
|
|
17
21
|
export default build('tnxbsv', () => {
|
|
18
22
|
const components = Object.assign({}, tnxvue.components, {
|
|
19
|
-
Button, EnumSelect, Loading,
|
|
23
|
+
Button, Dialog, EnumSelect, Form, FormGroup, Loading, Paged, QueryTable, Select,
|
|
20
24
|
});
|
|
21
25
|
|
|
22
|
-
const tnxbsv = Object.assign({}, tnxvue, {
|
|
26
|
+
const tnxbsv = Object.assign({}, tnxjq, tnxvue, {
|
|
27
|
+
libs: Object.assign({}, tnxjq.libs, tnxvue.libs, {BootstrapVue}),
|
|
23
28
|
components,
|
|
24
29
|
componentDefaultApp: undefined, // 组件的默认app,从服务端获取数据的组件以此为远程请求的默认app
|
|
25
30
|
_dialogs: [], // 对话框堆栈
|
|
26
31
|
dialog(content, title, buttons, options, contentProps) {
|
|
27
|
-
|
|
32
|
+
let id = new Date().getTime();
|
|
33
|
+
let containerId = 'dialog-container-' + id;
|
|
34
|
+
let componentDefinition = Object.assign({}, Dialog,);
|
|
35
|
+
let dialogVm = window.tnx.createVueInstance(componentDefinition, null, {
|
|
36
|
+
modelValue: true,
|
|
37
|
+
id: id,
|
|
38
|
+
container: '#' + containerId,
|
|
39
|
+
title,
|
|
40
|
+
content,
|
|
41
|
+
contentProps,
|
|
42
|
+
buttons,
|
|
43
|
+
});
|
|
44
|
+
const dialogContainer = document.createElement('div');
|
|
45
|
+
dialogContainer.className = 'tnxbsv-dialog-container';
|
|
46
|
+
dialogContainer.id = containerId;
|
|
47
|
+
document.body.appendChild(dialogContainer);
|
|
48
|
+
let dialog = dialogVm.mount(dialogContainer);
|
|
49
|
+
dialog.onHidden = this.util.function.after(dialog.onHidden, () => {
|
|
50
|
+
dialogVm.unmount();
|
|
51
|
+
this._dialogs.remove(dialog);
|
|
52
|
+
document.body.removeChild(dialogContainer);
|
|
53
|
+
});
|
|
54
|
+
this._dialogs.push(dialog);
|
|
55
|
+
return dialogVm;
|
|
28
56
|
},
|
|
29
57
|
});
|
|
30
58
|
|
|
31
|
-
tnxbsv.libs.BootstrapVue = BootstrapVue;
|
|
32
|
-
|
|
33
59
|
tnxbsv.install = tnxbsv.util.function.around(tnxbsv.install, function (install, vm) {
|
|
34
60
|
install.call(tnxbsv, vm);
|
|
35
61
|
vm.use(BootstrapVue.createBootstrap());
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-dialog
|
|
3
|
-
|
|
2
|
+
<el-dialog class="tnxel-dialog"
|
|
3
|
+
:data-v-id="id"
|
|
4
4
|
v-model="visible"
|
|
5
5
|
destroy-on-close
|
|
6
6
|
append-to-body
|
|
@@ -34,8 +34,6 @@
|
|
|
34
34
|
import $ from 'cash-dom';
|
|
35
35
|
import DialogContent from './DialogContent.vue';
|
|
36
36
|
|
|
37
|
-
const util = window.tnx.util;
|
|
38
|
-
|
|
39
37
|
export default {
|
|
40
38
|
name: 'TnxelDialog',
|
|
41
39
|
components: {
|
|
@@ -168,7 +166,7 @@ export default {
|
|
|
168
166
|
});
|
|
169
167
|
|
|
170
168
|
if (observe) {
|
|
171
|
-
this.heightChangeObserver = util.dom.observeHeightChange($dialog[0], this.locate);
|
|
169
|
+
this.heightChangeObserver = window.tnx.util.dom.observeHeightChange($dialog[0], this.locate);
|
|
172
170
|
}
|
|
173
171
|
}
|
|
174
172
|
},
|
|
@@ -190,7 +188,7 @@ export default {
|
|
|
190
188
|
const vm = this;
|
|
191
189
|
this.beforeClose(function () {
|
|
192
190
|
if (typeof callback === 'function') {
|
|
193
|
-
vm.options.onClosed = util.function.around(vm.options.onClosed, function (onClosed) {
|
|
191
|
+
vm.options.onClosed = window.tnx.util.function.around(vm.options.onClosed, function (onClosed) {
|
|
194
192
|
if (onClosed) {
|
|
195
193
|
onClosed();
|
|
196
194
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="tnxel-
|
|
2
|
+
<div class="tnxel-paged-wrapper"
|
|
3
3
|
:class="{'justify-content-center': align === 'center', 'justify-content-end': align === 'right'}" v-if="model">
|
|
4
4
|
<slot :paged="value" :change="change" :background="background">
|
|
5
5
|
<el-pagination :layout="layout" :background="background"
|
|
@@ -79,16 +79,16 @@ export default {
|
|
|
79
79
|
</script>
|
|
80
80
|
|
|
81
81
|
<style>
|
|
82
|
-
.tnxel-
|
|
82
|
+
.tnxel-paged-wrapper {
|
|
83
83
|
display: flex;
|
|
84
84
|
padding: 0.5rem 0;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
.tnxel-
|
|
87
|
+
.tnxel-paged-wrapper .el-pagination {
|
|
88
88
|
padding: 0;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
.tnxel-
|
|
91
|
+
.tnxel-paged-wrapper .el-pagination__page-size {
|
|
92
92
|
color: var(--el-text-color-regular);
|
|
93
93
|
margin-right: 1rem;
|
|
94
94
|
}
|
package/src/tnxvue.js
CHANGED
|
@@ -96,7 +96,6 @@ export default build('tnxvue', () => {
|
|
|
96
96
|
} else if (window.tnx.router.instance) {
|
|
97
97
|
vm.config.globalProperties.$router = window.tnx.router.instance;
|
|
98
98
|
}
|
|
99
|
-
// vm.config.unwrapInjectedRef = true;
|
|
100
99
|
window.tnx.app.eventBus = window.tnx.app.eventBus || mitt();
|
|
101
100
|
window.tnx.app.eventBus.once = function (name, handler) {
|
|
102
101
|
this.all.set(name, [handler]);
|