@truenewx/tnxvue3 3.0.5 → 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 +3 -3
- 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 +53 -43
- 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/tags-input/TagsInput.vue +64 -0
- package/src/bootstrap-vue/tnxbsv.css +36 -0
- package/src/bootstrap-vue/tnxbsv.js +110 -2
- package/src/element-plus/tnxel.js +2 -3
- package/src/tnxvue.js +2 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TnxbsvCascader v-model="model"
|
|
3
|
+
:options="region.subs"
|
|
4
|
+
:props="{
|
|
5
|
+
value: 'code',
|
|
6
|
+
label: 'caption',
|
|
7
|
+
children: 'subs',
|
|
8
|
+
}"
|
|
9
|
+
:placeholder="placeholder"
|
|
10
|
+
:disabled="disabled"
|
|
11
|
+
:clearable="empty"
|
|
12
|
+
:parent-selectable="parentSelectable"
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
import TnxbsvCascader from '../cascader/Cascader.vue';
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'TnxbsvRegionCascader',
|
|
21
|
+
components: {TnxbsvCascader},
|
|
22
|
+
props: {
|
|
23
|
+
modelValue: String,
|
|
24
|
+
scope: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: () => 'CN',
|
|
27
|
+
},
|
|
28
|
+
maxLevel: {
|
|
29
|
+
type: [Number, String],
|
|
30
|
+
default: 3,
|
|
31
|
+
},
|
|
32
|
+
minLevel: {
|
|
33
|
+
type: [Number, String],
|
|
34
|
+
default: 3,
|
|
35
|
+
},
|
|
36
|
+
empty: {
|
|
37
|
+
type: Boolean,
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
placeholder: String,
|
|
41
|
+
disabled: Boolean,
|
|
42
|
+
change: Function, // 选中值变化后的事件处理函数,由于比element的change事件传递更多参数,所以以prop的形式指定,以尽量节省性能
|
|
43
|
+
app: {
|
|
44
|
+
type: String,
|
|
45
|
+
default: () => window.tnx.componentDefaultApp,
|
|
46
|
+
},
|
|
47
|
+
parentSelectable: Boolean,
|
|
48
|
+
},
|
|
49
|
+
data() {
|
|
50
|
+
return {
|
|
51
|
+
model: this.modelValue,
|
|
52
|
+
region: {},
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
watch: {
|
|
56
|
+
model(value) {
|
|
57
|
+
this.$emit('update:modelValue', value);
|
|
58
|
+
this.triggerChange(value);
|
|
59
|
+
},
|
|
60
|
+
modelValue() {
|
|
61
|
+
this.model = this.getModel();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
mounted() {
|
|
65
|
+
window.tnx.app.rpc.loadRegion(this.scope, parseInt(this.maxLevel), region => {
|
|
66
|
+
this.region = region;
|
|
67
|
+
this.model = this.getModel();
|
|
68
|
+
}, {
|
|
69
|
+
app: this.app,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
methods: {
|
|
73
|
+
triggerChange(value) {
|
|
74
|
+
if (this.change) {
|
|
75
|
+
let item = this.getItem(this.region.subs, value);
|
|
76
|
+
this.change(item);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
getItem(items, value) {
|
|
80
|
+
if (items && value !== undefined) {
|
|
81
|
+
for (let item of items) {
|
|
82
|
+
if (item.code === value) {
|
|
83
|
+
return item;
|
|
84
|
+
}
|
|
85
|
+
let sub = this.getItem(item.subs, value);
|
|
86
|
+
if (sub) {
|
|
87
|
+
return sub;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
},
|
|
93
|
+
getModel() {
|
|
94
|
+
if (this.region) {
|
|
95
|
+
let items = this.region.subs;
|
|
96
|
+
if (items && items.length) {
|
|
97
|
+
let item = this.getItem(items, this.modelValue);
|
|
98
|
+
if (item) {
|
|
99
|
+
return item.code;
|
|
100
|
+
} else { // 如果当前值找不到匹配的选项,则需要考虑是设置为空还是默认选项
|
|
101
|
+
if (!this.empty) { // 如果不能为空,则默认选中第一个叶子节点选项
|
|
102
|
+
let firstItem = items[0];
|
|
103
|
+
while (firstItem.subs && firstItem.subs.length) {
|
|
104
|
+
firstItem = firstItem.subs[0];
|
|
105
|
+
}
|
|
106
|
+
return firstItem ? firstItem.code : null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<style>
|
|
118
|
+
|
|
119
|
+
</style>
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<BFormRadioGroup class="tnxbsv-select tnxbsv-radio-group"
|
|
3
|
+
v-model="model"
|
|
4
|
+
:options="items"
|
|
5
|
+
:value-field="valueName"
|
|
6
|
+
:text-field="textName"
|
|
7
|
+
:buttons="selector === 'radio-button'"
|
|
8
|
+
button-variant="outline-primary"
|
|
9
|
+
v-if="items && (selector==='radio' || selector === 'radio-button')"/>
|
|
2
10
|
<BDropdown class="tnxbsv-select tnxbsv-dropdown"
|
|
3
11
|
:key="groupKey"
|
|
4
12
|
:text="currentText"
|
|
5
13
|
:variant="theme"
|
|
6
14
|
:size="size"
|
|
7
|
-
v-if="selector==='dropdown'">
|
|
15
|
+
v-else-if="selector==='dropdown'">
|
|
8
16
|
<BDropdownItem :active="isSelected(emptyValue)" @click="select(emptyValue)" v-if="empty">
|
|
9
17
|
<span>{{ emptyText || ' ' }}</span>
|
|
10
18
|
</BDropdownItem>
|
|
@@ -21,7 +29,7 @@
|
|
|
21
29
|
</BDropdownItem>
|
|
22
30
|
</template>
|
|
23
31
|
<BDropdownItem v-else>
|
|
24
|
-
<
|
|
32
|
+
<LoadingIcon/>
|
|
25
33
|
</BDropdownItem>
|
|
26
34
|
</BDropdown>
|
|
27
35
|
<BFormSelect class="tnxbsv-select"
|
|
@@ -49,20 +57,20 @@
|
|
|
49
57
|
</template>
|
|
50
58
|
</BFormSelect>
|
|
51
59
|
<div class="flex-v-center" v-else>
|
|
52
|
-
<
|
|
60
|
+
<LoadingIcon/>
|
|
53
61
|
</div>
|
|
54
62
|
</template>
|
|
55
63
|
|
|
56
64
|
<script>
|
|
57
|
-
import {BDropdown, BDropdownItem, BFormSelect, BFormSelectOption} from 'bootstrap-vue-next';
|
|
58
|
-
import
|
|
65
|
+
import {BFormRadioGroup, BDropdown, BDropdownItem, BFormSelect, BFormSelectOption} from 'bootstrap-vue-next';
|
|
66
|
+
import LoadingIcon from '../loading-icon/LoadingIcon.vue';
|
|
59
67
|
|
|
60
68
|
export const isMultiSelector = function (selector) {
|
|
61
69
|
return selector === 'checkbox' || selector === 'tags' || selector === 'multi-select' || selector === 'texts';
|
|
62
70
|
}
|
|
63
71
|
export default {
|
|
64
72
|
name: 'TnxbsvSelect',
|
|
65
|
-
components: {BDropdown, BDropdownItem, BFormSelect, BFormSelectOption,
|
|
73
|
+
components: {BFormRadioGroup, BDropdown, BDropdownItem, BFormSelect, BFormSelectOption, LoadingIcon},
|
|
66
74
|
props: {
|
|
67
75
|
id: [Number, String],
|
|
68
76
|
modelValue: {
|
|
@@ -349,4 +357,9 @@ export default {
|
|
|
349
357
|
.tnxbsv-select[variant="danger"]:focus {
|
|
350
358
|
box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
|
|
351
359
|
}
|
|
360
|
+
|
|
361
|
+
.tnxbsv-radio-group.btn-group > .btn {
|
|
362
|
+
flex: none;
|
|
363
|
+
--bs-btn-padding-x: 1rem;
|
|
364
|
+
}
|
|
352
365
|
</style>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BFormTags class="tnxbsv-tags-input"
|
|
3
|
+
v-model="model"
|
|
4
|
+
:placeholder="placeholder"
|
|
5
|
+
:separator="separator"
|
|
6
|
+
:tag-variant="tagVariant || 'light'"
|
|
7
|
+
add-button-text="+"
|
|
8
|
+
:duplicate-tag-text="duplicateTagText"
|
|
9
|
+
remove-on-delete
|
|
10
|
+
/>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import {BFormTags} from 'bootstrap-vue-next';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
name: 'TnxbsvTagsInput',
|
|
18
|
+
components: {BFormTags},
|
|
19
|
+
props: {
|
|
20
|
+
modelValue: {
|
|
21
|
+
type: Array,
|
|
22
|
+
default: () => [],
|
|
23
|
+
},
|
|
24
|
+
placeholder: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: '输入后回车以添加',
|
|
27
|
+
},
|
|
28
|
+
separator: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: ','
|
|
31
|
+
},
|
|
32
|
+
tagVariant: String,
|
|
33
|
+
duplicateTagText: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: '标签重复',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
data() {
|
|
39
|
+
return {
|
|
40
|
+
model: this.modelValue,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
watch: {
|
|
44
|
+
modelValue() {
|
|
45
|
+
this.model = this.modelValue;
|
|
46
|
+
},
|
|
47
|
+
model() {
|
|
48
|
+
this.$emit('update:modelValue', this.model);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
methods: {}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<style>
|
|
56
|
+
.tnxbsv-tags-input .b-form-tag + .b-from-tags-field > div {
|
|
57
|
+
padding-left: 0.25rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.tnxbsv-tags-input .btn.b-form-tags-button {
|
|
61
|
+
--bs-btn-padding-x: 0.5rem;
|
|
62
|
+
margin-left: 0.5rem;
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
::placeholder {
|
|
2
|
+
color: var(--bs-tertiary-color) !important;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
::-webkit-input-placeholder {
|
|
6
|
+
color: var(--bs-tertiary-color) !important;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
::-moz-placeholder {
|
|
10
|
+
color: var(--bs-tertiary-color) !important;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
.link {
|
|
2
14
|
cursor: pointer;
|
|
3
15
|
color: var(--bs-link-color);
|
|
@@ -34,3 +46,27 @@
|
|
|
34
46
|
.accordion-body {
|
|
35
47
|
padding: 1rem;
|
|
36
48
|
}
|
|
49
|
+
|
|
50
|
+
.toast {
|
|
51
|
+
width: fit-content;
|
|
52
|
+
min-width: 5rem;
|
|
53
|
+
text-align: center;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.toast::before {
|
|
57
|
+
content: '';
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: 0;
|
|
60
|
+
left: 0;
|
|
61
|
+
width: 100%;
|
|
62
|
+
height: 100%;
|
|
63
|
+
background-color: rgba(255, 255, 255, 0.3);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.form-check {
|
|
67
|
+
margin-top: 0.125rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.form-check > * {
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
}
|
|
@@ -7,21 +7,37 @@ import 'bootstrap-vue-next/dist/bootstrap-vue-next.css';
|
|
|
7
7
|
import './tnxbsv.css';
|
|
8
8
|
|
|
9
9
|
import Button from './button/Button.vue';
|
|
10
|
+
import Cascader from './cascader/Cascader.vue';
|
|
10
11
|
import Dialog from './dialog/Dialog.vue';
|
|
11
12
|
import EnumSelect from './enum-select/EnumSelect.vue';
|
|
12
13
|
import Form from './form/Form.vue';
|
|
13
14
|
import FormGroup from './form/FormGroup.vue';
|
|
14
|
-
import
|
|
15
|
+
import LoadingIcon from './loading-icon/LoadingIcon.vue';
|
|
16
|
+
import LoadingOverlay from './loading-overlay/LoadingOverlay.vue';
|
|
15
17
|
import Paged from './paged/Paged.vue';
|
|
16
18
|
import QueryTable from './query-table/QueryTable.vue';
|
|
19
|
+
import RegionCascader from './region-cascader/RegionCascader.vue';
|
|
17
20
|
import Select from './select/Select.vue';
|
|
18
21
|
import SubmitForm from './submit-form/SubmitForm.vue';
|
|
22
|
+
import TagsInput from './tags-input/TagsInput.vue';
|
|
19
23
|
|
|
20
24
|
export const build = tnxvue.build;
|
|
21
25
|
|
|
22
26
|
export default build('tnxbsv', () => {
|
|
23
27
|
const components = Object.assign({}, tnxvue.components, {
|
|
24
|
-
Button,
|
|
28
|
+
Button,
|
|
29
|
+
Cascader,
|
|
30
|
+
Dialog,
|
|
31
|
+
EnumSelect,
|
|
32
|
+
Form,
|
|
33
|
+
FormGroup,
|
|
34
|
+
LoadingIcon,
|
|
35
|
+
Paged,
|
|
36
|
+
QueryTable,
|
|
37
|
+
RegionCascader,
|
|
38
|
+
Select,
|
|
39
|
+
SubmitForm,
|
|
40
|
+
TagsInput,
|
|
25
41
|
});
|
|
26
42
|
|
|
27
43
|
const tnxbsv = Object.assign({}, tnxjq, tnxvue, {
|
|
@@ -55,6 +71,98 @@ export default build('tnxbsv', () => {
|
|
|
55
71
|
this._dialogs.push(dialog);
|
|
56
72
|
return dialogVm;
|
|
57
73
|
},
|
|
74
|
+
_closeMessage() {
|
|
75
|
+
this.hideLoading();
|
|
76
|
+
this.removeToast();
|
|
77
|
+
},
|
|
78
|
+
toast(message, timeout, callback, options = {}) {
|
|
79
|
+
if (typeof timeout === 'function') {
|
|
80
|
+
options = callback || {};
|
|
81
|
+
callback = timeout;
|
|
82
|
+
timeout = undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this._closeMessage();
|
|
86
|
+
|
|
87
|
+
const div = document.createElement('div');
|
|
88
|
+
document.body.appendChild(div);
|
|
89
|
+
|
|
90
|
+
const Vue = window.tnx.libs.Vue;
|
|
91
|
+
const ToastComponent = {
|
|
92
|
+
components: {
|
|
93
|
+
BToast: BootstrapVue.BToast
|
|
94
|
+
},
|
|
95
|
+
setup() {
|
|
96
|
+
const visible = Vue.ref(true);
|
|
97
|
+
Vue.onMounted(() => {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
visible.value = false;
|
|
100
|
+
// 延迟移除组件,确保动画效果完成
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
window.tnx.toastInstance?.unmount();
|
|
103
|
+
try {
|
|
104
|
+
document.body.removeChild(div);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// 忽略异常
|
|
107
|
+
}
|
|
108
|
+
}, 500);
|
|
109
|
+
}, timeout || 1500);
|
|
110
|
+
});
|
|
111
|
+
return {visible};
|
|
112
|
+
},
|
|
113
|
+
render() {
|
|
114
|
+
return Vue.h(BootstrapVue.BToast, {
|
|
115
|
+
modelValue: this.visible,
|
|
116
|
+
variant: options.type || 'success',
|
|
117
|
+
static: true,
|
|
118
|
+
noCloseButton: true,
|
|
119
|
+
class: 'position-fixed',
|
|
120
|
+
style: {
|
|
121
|
+
top: '50%',
|
|
122
|
+
left: '50%',
|
|
123
|
+
transform: 'translate(-50%, -50%)',
|
|
124
|
+
zIndex: window.tnx.util.dom.minTopZIndex(),
|
|
125
|
+
}
|
|
126
|
+
}, () => message);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const instance = window.tnx.createVueInstance(ToastComponent);
|
|
131
|
+
instance.mount(div);
|
|
132
|
+
window.tnx.toastInstance = instance;
|
|
133
|
+
},
|
|
134
|
+
removeToast() {
|
|
135
|
+
if (window.tnx.toastInstance) {
|
|
136
|
+
window.tnx.toastInstance.unmount();
|
|
137
|
+
try {
|
|
138
|
+
document.body.removeChild(window.tnx.toastInstance._container);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// 忽略异常
|
|
141
|
+
}
|
|
142
|
+
window.tnx.toastInstance = null;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
showLoading(message = '', options) {
|
|
146
|
+
this._closeMessage();
|
|
147
|
+
|
|
148
|
+
let div = document.createElement('div');
|
|
149
|
+
document.body.appendChild(div);
|
|
150
|
+
let instance = window.tnx.createVueInstance(LoadingOverlay, null, {message});
|
|
151
|
+
let component = instance.mount(div);
|
|
152
|
+
window.tnx.loadingInstance = instance;
|
|
153
|
+
window.tnx.app.eventBus.emit('tnx.showLoading', options);
|
|
154
|
+
return component;
|
|
155
|
+
},
|
|
156
|
+
hideLoading() {
|
|
157
|
+
if (window.tnx.loadingInstance) {
|
|
158
|
+
window.tnx.loadingInstance.unmount();
|
|
159
|
+
document.body.removeChild(window.tnx.loadingInstance._container);
|
|
160
|
+
window.tnx.loadingInstance = null;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
closeLoading() {
|
|
164
|
+
this.hideLoading();
|
|
165
|
+
},
|
|
58
166
|
});
|
|
59
167
|
|
|
60
168
|
tnxbsv.install = tnxbsv.util.function.around(tnxbsv.install, function (install, vm) {
|
|
@@ -218,9 +218,8 @@ export default build('tnxel', () => {
|
|
|
218
218
|
this.closeLoading();
|
|
219
219
|
},
|
|
220
220
|
_handleZIndex(selector) {
|
|
221
|
-
const util = this.util;
|
|
222
221
|
setTimeout(function () {
|
|
223
|
-
const topZIndex = util.dom.minTopZIndex(2);
|
|
222
|
+
const topZIndex = window.tnx.util.dom.minTopZIndex(2);
|
|
224
223
|
if (selector.endsWith(':last')) {
|
|
225
224
|
selector = selector.substring(0, selector.length - ':last'.length);
|
|
226
225
|
}
|
|
@@ -355,7 +354,7 @@ export default build('tnxel', () => {
|
|
|
355
354
|
try {
|
|
356
355
|
window.tnx.loadingInstance = ElLoading.service(options);
|
|
357
356
|
this._handleZIndex('.el-loading-mask');
|
|
358
|
-
|
|
357
|
+
window.tnx.app.eventBus.emit('tnx.showLoading', options);
|
|
359
358
|
} catch (e) {
|
|
360
359
|
window.tnx.loadingInstance = null;
|
|
361
360
|
console.error(e);
|
package/src/tnxvue.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 基于Vue 3的扩展支持
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import tnxcore from '../../core/src/tnxcore';
|
|
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';
|