@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.
@@ -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 || '&nbsp;' }}</span>
10
18
  </BDropdownItem>
@@ -21,7 +29,7 @@
21
29
  </BDropdownItem>
22
30
  </template>
23
31
  <BDropdownItem v-else>
24
- <Loading/>
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
- <Loading/>
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 Loading from '../loading/Loading.vue';
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, Loading},
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 Loading from './loading/Loading.vue';
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, Dialog, EnumSelect, Form, FormGroup, Loading, Paged, QueryTable, Select, SubmitForm,
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
- this.app.eventBus.emit('tnx.showLoading', options);
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
- // import tnxcore from '@truenewx/tnxcore';
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';