@truenewx/tnxvue3 3.0.0 → 3.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truenewx/tnxvue3",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "互联网技术解决方案:Vue3扩展支持",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -20,11 +20,12 @@
20
20
  },
21
21
  "peerDependencies": {
22
22
  "element-plus": "~2.8.0",
23
+ "bootstrap-vue-next": "~0.26.0",
23
24
  "vue": "~3.5.0",
24
25
  "vue-router": "~4.4.0"
25
26
  },
26
27
  "dependencies": {
27
- "@truenewx/tnxcore": "3.0.0",
28
+ "@truenewx/tnxcore": "3.0.2",
28
29
  "@element-plus/icons-vue": "2.3.1",
29
30
  "async-validator": "4.2.5",
30
31
  "mitt": "3.0.1"
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <BButton>
3
+ <i :class="icon" v-if="icon"></i>
4
+ <span><slot></slot></span>
5
+ </BButton>
6
+ </template>
7
+
8
+ <script>
9
+ import {BButton} from 'bootstrap-vue-next';
10
+
11
+ export default {
12
+ name: 'TnxbsvButton',
13
+ components: {BButton},
14
+ props: {
15
+ icon: String,
16
+ },
17
+ data() {
18
+ return {};
19
+ },
20
+ methods: {}
21
+ }
22
+ </script>
23
+
24
+ <style>
25
+
26
+ </style>
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <Select v-model="model"
3
+ :id="id"
4
+ :selector="selector"
5
+ :items="items"
6
+ value-name="key" text-name="caption" index-name="searchIndex"
7
+ :default-value="defaultValue"
8
+ :empty="empty"
9
+ :empty-value="emptyValue"
10
+ :placeholder="placeholder"
11
+ :disabled="disabled"
12
+ :filterable="filterable"
13
+ :theme="theme"
14
+ :size="size"
15
+ :tag-click="tagClick"
16
+ :change="change"
17
+ />
18
+ </template>
19
+
20
+ <script>
21
+ import Select, {isMultiSelector} from '../select/Select.vue';
22
+
23
+ export default {
24
+ name: 'TnxbsvEnumSelect',
25
+ components: {Select},
26
+ props: {
27
+ id: [Number, String],
28
+ modelValue: [String, Number, Boolean, Array],
29
+ selector: String,
30
+ type: {
31
+ type: String,
32
+ required: true,
33
+ },
34
+ subtype: String,
35
+ defaultValue: String,
36
+ empty: {
37
+ type: [Boolean, String],
38
+ default: false,
39
+ },
40
+ emptyValue: {
41
+ type: [String, Number, Boolean, Array],
42
+ default: '', // 设为null会告警,暂时先设为空字符,在非字符串类型的情况下是否有问题,有待检验
43
+ },
44
+ placeholder: String,
45
+ disabled: Boolean,
46
+ tagClick: Function,
47
+ change: Function,
48
+ grouped: {
49
+ type: Boolean,
50
+ default: false,
51
+ },
52
+ filterable: Boolean,
53
+ theme: String,
54
+ size: String,
55
+ app: {
56
+ type: String,
57
+ default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
58
+ },
59
+ },
60
+ emits: ['update:modelValue'],
61
+ data() {
62
+ return {
63
+ model: this.modelValue,
64
+ items: null,
65
+ };
66
+ },
67
+ watch: {
68
+ model(value) {
69
+ this.$emit('update:modelValue', value);
70
+ },
71
+ modelValue() {
72
+ this.initModel();
73
+ },
74
+ type() {
75
+ this.init();
76
+ },
77
+ subtype() {
78
+ this.init();
79
+ }
80
+ },
81
+ mounted() {
82
+ this.init();
83
+ },
84
+ methods: {
85
+ init() {
86
+ if (typeof this.type === 'string') {
87
+ if (this.type.toLowerCase() === 'boolean') {
88
+ this.items = [{
89
+ key: true,
90
+ caption: true.toText(),
91
+ }, {
92
+ key: false,
93
+ caption: false.toText(),
94
+ }];
95
+ this.initModel();
96
+ } else {
97
+ let vm = this;
98
+ window.tnx.app.rpc.loadEnumItems(this.type, this.subtype, function (items) {
99
+ vm.items = items;
100
+ vm.initModel();
101
+ }, {
102
+ app: this.app,
103
+ });
104
+ }
105
+ }
106
+ },
107
+ initModel() {
108
+ let oldModel = this.model;
109
+ this.model = this.modelValue;
110
+ if (isMultiSelector(this.selector)) {
111
+ return;
112
+ }
113
+ if ((this.model === undefined || this.model === null) && !this.empty && this.items && this.items.length) {
114
+ let item = this.items[0];
115
+ this.model = item.key;
116
+ if (this.model !== oldModel && this.change) {
117
+ this.change(item);
118
+ }
119
+ }
120
+ },
121
+ getText(value) {
122
+ // 暂不支持分组枚举类型
123
+ if (this.$refs.select) {
124
+ return this.$refs.select.getText(value);
125
+ }
126
+ return undefined;
127
+ },
128
+ disableItem(itemKey, disabled, reverseOther) {
129
+ this.$refs.select.disableItem(itemKey, disabled, reverseOther);
130
+ },
131
+ }
132
+ }
133
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <span class="spinner-border" :class="extraClass" role="status">
3
+ <span class="visually-hidden">{{ text }}</span>
4
+ </span>
5
+ </template>
6
+
7
+ <script>
8
+
9
+ export default {
10
+ name: 'TnxbsvLoading',
11
+ props: {
12
+ small: {
13
+ type: Boolean,
14
+ default: true,
15
+ },
16
+ theme: {
17
+ type: String,
18
+ default: 'secondary',
19
+ },
20
+ text: {
21
+ type: String,
22
+ default: '加载中...',
23
+ }
24
+ },
25
+ data() {
26
+ return {};
27
+ },
28
+ computed: {
29
+ extraClass() {
30
+ let extraClass = 'text-' + this.theme;
31
+ if (this.small) {
32
+ extraClass += ' spinner-border-sm';
33
+ }
34
+ return extraClass;
35
+ },
36
+ },
37
+ methods: {}
38
+ }
39
+ </script>
40
+
41
+ <style>
42
+
43
+ </style>
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <div class="tnxbsv-pagination" :class="'justify-content-' + align" v-if="paged">
3
+ <div class="pagination-text">
4
+ <span>每页</span>
5
+ <Select v-model="pageSize" :items="pageSizeItems" v-if="pageSizeChangeable"/>
6
+ <span class="mx-1" v-else>{{ pageSize }}</span>
7
+ <span>条,共</span>
8
+ <span class="mx-1">{{ paged.total }}</span>
9
+ <span>条</span>
10
+ </div>
11
+ <BPagination v-model="pageNo"
12
+ :total-rows="paged.total"
13
+ :per-page="paged.pageSize"
14
+ :aria-controls="ariaControls"
15
+ :aria-label="ariaLabel"
16
+ />
17
+ </div>
18
+ </template>
19
+
20
+ <script>
21
+ import {BPagination} from 'bootstrap-vue-next';
22
+ import Select from '../select/Select.vue';
23
+
24
+ export default {
25
+ name: 'TnxbsvPagination',
26
+ components: {BPagination, Select},
27
+ props: {
28
+ paged: Object,
29
+ pageSizeChangeable: Boolean,
30
+ align: {
31
+ type: String,
32
+ default: 'end', // start | center | end
33
+ },
34
+ ariaControls: String,
35
+ ariaLabel: String,
36
+ query: Function,
37
+ },
38
+ data() {
39
+ return {
40
+ pageSize: this.paged?.pageSize || 20,
41
+ pageNo: this.paged?.pageNo || 1,
42
+ };
43
+ },
44
+ computed: {
45
+ pageSizeItems() {
46
+ return [{
47
+ value: 10,
48
+ text: '10',
49
+ }, {
50
+ value: 20,
51
+ text: '20',
52
+ }, {
53
+ value: 50,
54
+ text: '50',
55
+ }];
56
+ },
57
+ },
58
+ watch: {
59
+ pageNo() {
60
+ this.toQuery();
61
+ },
62
+ pageSize() {
63
+ this.toQuery();
64
+ },
65
+ },
66
+ methods: {
67
+ toQuery() {
68
+ if (this.query) {
69
+ this.query({
70
+ pageSize: this.pageSize,
71
+ pageNo: this.pageNo,
72
+ });
73
+ }
74
+ },
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <style>
80
+ .tnxbsv-pagination {
81
+ width: 100%;
82
+ display: flex;
83
+ align-items: center;
84
+ }
85
+
86
+ .tnxbsv-pagination .pagination-text {
87
+ display: flex;
88
+ align-items: center;
89
+ color: var(--bs-secondary-color);
90
+ margin: 0 0.5rem;
91
+ white-space: nowrap;
92
+ }
93
+
94
+ .tnxbsv-pagination .form-select {
95
+ width: 4.5rem;
96
+ margin: 0 0.5rem;
97
+ }
98
+
99
+ .tnxbsv-pagination ul {
100
+ margin-bottom: 0;
101
+ }
102
+ </style>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <BTable class="tnxbsv-query-table"
3
+ :id="id"
4
+ :items="result.records || []"
5
+ :fields="fields"
6
+ :busy="result.records === null"
7
+ >
8
+ <template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
9
+ <slot :name="slot" v-bind="scope"></slot>
10
+ </template>
11
+ <template #custom-foot>
12
+ <tr>
13
+ <td colspan="100%">
14
+ <Pagination :paged="result.paged"
15
+ :query="query"
16
+ :page-size-changeable="pageSizeChangeable"
17
+ :aria-controls="id"
18
+ />
19
+ </td>
20
+ </tr>
21
+ </template>
22
+ </BTable>
23
+ </template>
24
+
25
+ <script>
26
+ import {BTable} from 'bootstrap-vue-next';
27
+ import Pagination from '../pagination/Pagination.vue';
28
+
29
+ export default {
30
+ name: 'TnxbsvQueryTable',
31
+ components: {BTable, Pagination},
32
+ props: {
33
+ id: {
34
+ type: String,
35
+ default: () => {
36
+ return 'query-table-' + window.tnx.util.string.uuid32();
37
+ },
38
+ },
39
+ url: {
40
+ type: String,
41
+ required: true,
42
+ },
43
+ params: {
44
+ type: Object,
45
+ required: true,
46
+ },
47
+ fields: {
48
+ type: Array,
49
+ required: true,
50
+ },
51
+ init: Boolean,
52
+ pageSizeChangeable: Boolean,
53
+ },
54
+ data() {
55
+ return {
56
+ result: {
57
+ records: null,
58
+ paged: null,
59
+ },
60
+ };
61
+ },
62
+ mounted() {
63
+ if (this.init) {
64
+ this.query();
65
+ }
66
+ },
67
+ methods: {
68
+ query(paging) {
69
+ let params = Object.assign({}, this.params, paging);
70
+ this.result.records = null;
71
+ window.tnx.app.rpc.get(this.url, params, result => {
72
+ this.result = result;
73
+ });
74
+ },
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <style>
80
+ .tnxbsv-query-table th,
81
+ .tnxbsv-query-table td {
82
+ vertical-align: middle;
83
+ }
84
+ </style>
@@ -0,0 +1,352 @@
1
+ <template>
2
+ <BDropdown class="tnxbsv-select tnxbsv-dropdown"
3
+ :key="groupKey"
4
+ :text="currentText"
5
+ :variant="theme"
6
+ :size="size"
7
+ v-if="selector==='dropdown'">
8
+ <BDropdownItem :active="isSelected(emptyValue)" @click="select(emptyValue)" v-if="empty">
9
+ <span>{{ emptyText || '&nbsp;' }}</span>
10
+ </BDropdownItem>
11
+ <template v-if="items">
12
+ <BDropdownItem v-for="(item, index) of items" :key="index"
13
+ :active="isSelected(item[valueName])"
14
+ @click="select(item[valueName])"
15
+ >
16
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
17
+ <template v-else>
18
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
19
+ <span>{{ item[textName] }}</span>
20
+ </template>
21
+ </BDropdownItem>
22
+ </template>
23
+ <BDropdownItem v-else>
24
+ <Loading/>
25
+ </BDropdownItem>
26
+ </BDropdown>
27
+ <BFormSelect class="tnxbsv-select"
28
+ v-model="model"
29
+ :key="groupKey"
30
+ :variant="theme"
31
+ :options="items"
32
+ :value-field="valueName"
33
+ :text-field="textName"
34
+ :size="size"
35
+ :required="!empty"
36
+ v-else-if="items">
37
+ <template #default>
38
+ {{ currentText || placeholder }}
39
+ </template>
40
+ <template #first v-if="empty">
41
+ <BFormSelectOption :value="emptyValue">{{ emptyText || '&nbsp;' }}</BFormSelectOption>
42
+ </template>
43
+ <template #option="{value, text}">
44
+ <slot name="option" :item="getItem(value)" v-if="$slots.option"></slot>
45
+ <template v-else>
46
+ <i :class="getItem(value)[iconName]" v-if="getItem(value)[iconName]"></i>
47
+ <span>{{ text }}</span>
48
+ </template>
49
+ </template>
50
+ </BFormSelect>
51
+ <div class="flex-v-center" v-else>
52
+ <Loading/>
53
+ </div>
54
+ </template>
55
+
56
+ <script>
57
+ import {BDropdown, BDropdownItem, BFormSelect, BFormSelectOption} from 'bootstrap-vue-next';
58
+ import Loading from '../loading/Loading.vue';
59
+
60
+ export const isMultiSelector = function (selector) {
61
+ return selector === 'checkbox' || selector === 'tags' || selector === 'multi-select' || selector === 'texts';
62
+ }
63
+ export default {
64
+ name: 'TnxbsvSelect',
65
+ components: {BDropdown, BDropdownItem, BFormSelect, BFormSelectOption, Loading},
66
+ props: {
67
+ id: [Number, String],
68
+ modelValue: {
69
+ type: [String, Number, Boolean, Array],
70
+ default: null,
71
+ },
72
+ selector: String,
73
+ items: Array,
74
+ valueName: {
75
+ type: String,
76
+ default: 'value',
77
+ },
78
+ textName: {
79
+ type: String,
80
+ default: 'text',
81
+ },
82
+ indexName: {
83
+ type: String,
84
+ default: 'index',
85
+ },
86
+ iconName: {
87
+ type: String,
88
+ default: 'icon',
89
+ },
90
+ defaultValue: {
91
+ type: [String, Number, Boolean, Array],
92
+ default: null,
93
+ },
94
+ empty: {
95
+ type: [Boolean, String],
96
+ default: false,
97
+ },
98
+ emptyValue: {
99
+ type: [String, Number, Boolean, Array],
100
+ default: '', // 设为null会告警,暂时先设为空字符,在非字符串类型的情况下是否有问题,有待检验
101
+ },
102
+ emptyClass: String,
103
+ placeholder: String,
104
+ disabled: Boolean,
105
+ tagClick: Function, // 点击一个标签选项时调用,如果返回false,则选项不会被选中
106
+ change: Function, // 选中值变化后的事件处理函数,比change事件传递更多参数数据
107
+ filterable: Boolean,
108
+ theme: {
109
+ type: String,
110
+ default: 'primary',
111
+ },
112
+ size: String,
113
+ border: Boolean,
114
+ },
115
+ emits: ['update:modelValue', 'change'],
116
+ data() {
117
+ let model = this.getModel(this.items);
118
+ if (model !== this.modelValue) {
119
+ this.$emit('update:modelValue', model);
120
+ }
121
+ return {
122
+ groupKey: new Date().getTime(),
123
+ model: model,
124
+ hiddenValues: [],
125
+ };
126
+ },
127
+ computed: {
128
+ emptyText() {
129
+ return typeof this.empty === 'string' ? this.empty : '';
130
+ },
131
+ currentText() {
132
+ let item = this.getItem(this.model);
133
+ return item ? item[this.textName] : this.emptyText;
134
+ },
135
+ modelEqualsModelValue() {
136
+ if (Array.isArray(this.model) && Array.isArray(this.modelValue)) {
137
+ return this.model.equals(this.modelValue);
138
+ } else {
139
+ return this.model === this.modelValue;
140
+ }
141
+ },
142
+ firstEnabledItem() {
143
+ for (let item of this.items) {
144
+ if (!item.disabled) {
145
+ return item;
146
+ }
147
+ }
148
+ return undefined;
149
+ },
150
+ },
151
+ watch: {
152
+ model(newValue, oldValue) {
153
+ if (!this.modelEqualsModelValue) {
154
+ this.$emit('update:modelValue', newValue);
155
+ // 新旧值不同时为空才触发变更事件
156
+ const util = window.tnx.util;
157
+ if (util.object.isNotEmpty(newValue) || util.object.isNotEmpty(oldValue)) {
158
+ let vm = this;
159
+ // 确保变更事件在值变更应用后再触发
160
+ this.$nextTick(function () {
161
+ vm.triggerChange(newValue);
162
+ });
163
+ }
164
+ }
165
+ },
166
+ modelValue() {
167
+ this.model = this.getModel(this.items);
168
+ },
169
+ items(items) {
170
+ this.model = this.getModel(items);
171
+ },
172
+ },
173
+ methods: {
174
+ isMulti() {
175
+ return isMultiSelector(this.selector);
176
+ },
177
+ triggerChange(value) {
178
+ if (this.change) {
179
+ let item = undefined;
180
+ if (this.isMulti()) {
181
+ item = [];
182
+ if (Array.isArray(value)) {
183
+ for (let v of value) {
184
+ item.push(this.getItem(v));
185
+ }
186
+ }
187
+ } else {
188
+ item = this.getItem(value);
189
+ }
190
+ this.change(item, this.id);
191
+ } else {
192
+ this.$emit('change', value);
193
+ }
194
+ },
195
+ getItem(value) {
196
+ if (this.empty && value === this.emptyValue) {
197
+ let item = {};
198
+ item[this.valueName] = this.emptyValue;
199
+ item[this.textName] = this.emptyText;
200
+ return item;
201
+ }
202
+ if (value !== undefined && value !== null && this.items) {
203
+ for (let item of this.items) {
204
+ if ((item[this.valueName] + '') === (value + '')) {
205
+ return item;
206
+ }
207
+ }
208
+ }
209
+ return undefined;
210
+ },
211
+ getText(value) {
212
+ let item = this.getItem(value);
213
+ return item ? item[this.textName] : undefined;
214
+ },
215
+ getModel(items) {
216
+ const util = window.tnx.util;
217
+ let model = this.modelValue;
218
+ if (util.object.isNull(model)) {
219
+ model = this.defaultValue;
220
+ }
221
+ if (this.isMulti()) { // 多选时需确保值为数组
222
+ if (util.object.isNull(model)) {
223
+ return [];
224
+ }
225
+ if (!Array.isArray(model)) {
226
+ model = [model];
227
+ }
228
+ if (items && items.length) {
229
+ // 多选时,如果model中有值不在items中,则去掉
230
+ for (let i = model.length - 1; i >= 0; i--) {
231
+ let item = this.getItem(model[i]);
232
+ if (!item) {
233
+ model.splice(i, 1);
234
+ }
235
+ }
236
+ } else {
237
+ model = [];
238
+ }
239
+ return model;
240
+ }
241
+ if (util.object.isNull(model)) {
242
+ return null;
243
+ }
244
+ if (items && items.length) {
245
+ let item = this.getItem(model);
246
+ if (item) {
247
+ return item[this.valueName];
248
+ } else { // 如果当前值找不到匹配的选项,则需要考虑是设置为空还是默认选项
249
+ if (!this.empty) { // 如果不能为空,则默认选中第一个可用选项
250
+ let firstItem = this.firstEnabledItem;
251
+ if (firstItem && firstItem[this.valueName]) {
252
+ return firstItem[this.valueName];
253
+ }
254
+ }
255
+ }
256
+ }
257
+ return model;
258
+ },
259
+ filter(keyword) {
260
+ for (let item of this.items) {
261
+ let itemValue = item[this.valueName];
262
+ let hiddenIndex = this.hiddenValues.indexOf(itemValue);
263
+ if (this.matchesItem(item, keyword)) {
264
+ if (hiddenIndex >= 0) { // 匹配且原本已隐藏的,则取消隐藏
265
+ this.hiddenValues.splice(hiddenIndex, 1);
266
+ }
267
+ } else {
268
+ if (hiddenIndex < 0) { // 不匹配且原本未隐藏的,则进行隐藏
269
+ this.hiddenValues.push(itemValue);
270
+ }
271
+ }
272
+ }
273
+ },
274
+ matchesItem(item, keyword) {
275
+ return !keyword || window.tnx.util.string.matchesForEach(item[this.valueName], keyword)
276
+ || window.tnx.util.string.matchesForEach(item[this.textName], keyword)
277
+ || window.tnx.util.string.matchesForEach(item[this.indexName], keyword)
278
+ },
279
+ isSelected(value) {
280
+ if (Array.isArray(this.model)) {
281
+ return this.model.contains(value);
282
+ } else {
283
+ return this.model === value;
284
+ }
285
+ },
286
+ select(value, event) {
287
+ if (this.tagClick) {
288
+ let item = this.getItem(value);
289
+ if (item) {
290
+ if (this.tagClick(item) === false) {
291
+ return;
292
+ }
293
+ }
294
+ }
295
+ if (this.isMulti()) {
296
+ let index = this.model.indexOf(value);
297
+ if (index >= 0) {
298
+ this.model = this.model.filter(function (e, i) {
299
+ return i !== index;
300
+ });
301
+ } else {
302
+ this.model = this.model.concat([value]);
303
+ }
304
+ } else {
305
+ this.model = value;
306
+ }
307
+ if (event) {
308
+ event.currentTarget.blur();
309
+ }
310
+ },
311
+ clear() {
312
+ if (this.isMulti()) {
313
+ this.model = [];
314
+ } else {
315
+ this.model = null;
316
+ }
317
+ },
318
+ disableItem(itemValue, disabled, reverseOther) {
319
+ let values = Array.isArray(itemValue) ? itemValue : [itemValue];
320
+ for (let item of this.items) {
321
+ if (values.contains(item[this.valueName])) {
322
+ item.disabled = disabled;
323
+ // 如果禁用的选项已被选择,则需从已选择值中移除该选项的值
324
+ if (this.isMulti()) {
325
+ if (this.model.includes(itemValue)) {
326
+ this.model.remove(itemValue);
327
+ }
328
+ } else {
329
+ if (this.model === itemValue) {
330
+ this.model = undefined;
331
+ }
332
+ }
333
+ } else if (reverseOther) {
334
+ item.disabled = !disabled;
335
+ }
336
+ }
337
+ this.groupKey = new Date().getTime();
338
+ },
339
+ }
340
+ }
341
+ </script>
342
+
343
+ <style>
344
+ .tnxbsv-select[variant="danger"] {
345
+ border-color: var(--bs-danger-border-subtle);
346
+ color: var(--bs-danger);
347
+ }
348
+
349
+ .tnxbsv-select[variant="danger"]:focus {
350
+ box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
351
+ }
352
+ </style>
@@ -0,0 +1,31 @@
1
+ .link {
2
+ cursor: pointer;
3
+ color: var(--bs-link-color);
4
+ }
5
+
6
+ .form-inline {
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ align-items: center;
10
+ }
11
+
12
+ .form-inline-group {
13
+ display: flex;
14
+ flex-wrap: nowrap;
15
+ height: fit-content;
16
+ margin-bottom: 1rem;
17
+ width: fit-content;
18
+ }
19
+
20
+ .form-inline-group .col-form-label {
21
+ white-space: nowrap;
22
+ margin-right: 1rem;
23
+ }
24
+
25
+ .badge {
26
+ border: 1px solid transparent;
27
+ }
28
+
29
+ .badge.text-bg-light {
30
+ border-color: var(--bs-border-color);
31
+ }
@@ -0,0 +1,42 @@
1
+ // tnxbsv.js
2
+ import tnxvue from '../tnxvue.js';
3
+ import * as BootstrapVue from 'bootstrap-vue-next';
4
+ import 'bootstrap/dist/css/bootstrap.css';
5
+ import 'bootstrap-vue-next/dist/bootstrap-vue-next.css';
6
+ import './tnxbsv.css';
7
+
8
+ import Button from './button/Button.vue';
9
+ import EnumSelect from './enum-select/EnumSelect.vue';
10
+ import Loading from './loading/Loading.vue';
11
+ import Pagination from './pagination/Pagination.vue';
12
+ import QueryTable from './query-table/QueryTable.vue';
13
+ import Select from './select/Select.vue';
14
+
15
+ export const build = tnxvue.build;
16
+
17
+ export default build('tnxbsv', () => {
18
+ const components = Object.assign({}, tnxvue.components, {
19
+ Button, EnumSelect, Loading, Pagination, QueryTable, Select,
20
+ });
21
+
22
+ const tnxbsv = Object.assign({}, tnxvue, {
23
+ components,
24
+ componentDefaultApp: undefined, // 组件的默认app,从服务端获取数据的组件以此为远程请求的默认app
25
+ _dialogs: [], // 对话框堆栈
26
+ dialog(content, title, buttons, options, contentProps) {
27
+
28
+ },
29
+ });
30
+
31
+ tnxbsv.libs.BootstrapVue = BootstrapVue;
32
+
33
+ tnxbsv.install = tnxbsv.util.function.around(tnxbsv.install, function (install, vm) {
34
+ install.call(tnxbsv, vm);
35
+ vm.use(BootstrapVue.createBootstrap());
36
+ });
37
+
38
+ return tnxbsv;
39
+ });
40
+
41
+
42
+
@@ -1,41 +1,41 @@
1
- <template>
2
- <span v-if="item">{{ item.caption }}</span>
3
- </template>
4
-
5
- <script>
6
- export default {
7
- name: 'TnxelEnumView',
8
- props: {
9
- type: {
10
- type: String,
11
- required: true,
12
- },
13
- subtype: String,
14
- itemKey: {
15
- type: String,
16
- required: true,
17
- },
18
- app: {
19
- type: String,
20
- default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
21
- },
22
- },
23
- data() {
24
- return {
25
- item: null,
26
- };
27
- },
28
- mounted() {
29
- window.tnx.app.rpc.loadEnumType(this.type, this.subtype, enumType => {
30
- this.item = enumType.items.find(item => item.key === this.itemKey);
31
- }, {
32
- app: this.app,
33
- });
34
- },
35
- methods: {}
36
- }
37
- </script>
38
-
39
- <style scoped>
40
-
41
- </style>
1
+ <template>
2
+ <span v-if="item">{{ item.caption }}</span>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'TnxelEnumView',
8
+ props: {
9
+ type: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ subtype: String,
14
+ itemKey: {
15
+ type: String,
16
+ required: true,
17
+ },
18
+ app: {
19
+ type: String,
20
+ default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
21
+ },
22
+ },
23
+ data() {
24
+ return {
25
+ item: null,
26
+ };
27
+ },
28
+ mounted() {
29
+ window.tnx.app.rpc.loadEnumType(this.type, this.subtype, enumType => {
30
+ this.item = enumType.items.find(item => item.key === this.itemKey);
31
+ }, {
32
+ app: this.app,
33
+ });
34
+ },
35
+ methods: {}
36
+ }
37
+ </script>
38
+
39
+ <style scoped>
40
+
41
+ </style>
@@ -26,6 +26,7 @@
26
26
  <DocumentCopy v-else-if="value === 'DocumentCopy'"/>
27
27
  <Download v-else-if="value === 'Download'"/>
28
28
  <Edit v-else-if="value === 'Edit'"/>
29
+ <EditPen v-else-if="value === 'EditPen'"/>
29
30
  <Files v-else-if="value === 'Files'"/>
30
31
  <Finished v-else-if="value === 'Finished'"/>
31
32
  <Folder v-else-if="value === 'Folder'"/>
@@ -98,6 +99,7 @@ import {
98
99
  DocumentCopy,
99
100
  Download,
100
101
  Edit,
102
+ EditPen,
101
103
  Files,
102
104
  Finished,
103
105
  Folder,
@@ -169,6 +171,7 @@ const components = {
169
171
  DocumentCopy,
170
172
  Download,
171
173
  Edit,
174
+ EditPen,
172
175
  Files,
173
176
  Finished,
174
177
  Folder,
@@ -1,74 +1,74 @@
1
- <template>
2
- <el-input class="tnxel-input-dropdown"
3
- v-model="model"
4
- :disabled="disabled"
5
- >
6
- <template #append v-if="items?.length">
7
- <el-dropdown :trigger="trigger" @command="selectItem">
8
- <span class="el-dropdown-link">
9
- <Icon value="ArrowDown"/>
10
- </span>
11
- <template #dropdown>
12
- <el-dropdown-menu>
13
- <template v-for="(item, index) of items" :key="index">
14
- <el-dropdown-item
15
- :command="item[valueName]"
16
- :title="item[titleName]"
17
- >
18
- {{ item[textName] }}
19
- </el-dropdown-item>
20
- </template>
21
- </el-dropdown-menu>
22
- </template>
23
- </el-dropdown>
24
- </template>
25
- </el-input>
26
- </template>
27
-
28
- <script>
29
- import Icon from '../icon/Icon.vue';
30
-
31
- export default {
32
- name: 'TnxelInputDropdown',
33
- components: {Icon},
34
- props: {
35
- modelValue: String,
36
- items: Array,
37
- valueName: {
38
- type: String,
39
- default: 'value',
40
- },
41
- textName: {
42
- type: String,
43
- default: 'text',
44
- },
45
- titleName: {
46
- type: String,
47
- default: 'title',
48
- },
49
- trigger: String,
50
- disabled: Boolean,
51
- },
52
- data() {
53
- return {
54
- model: this.modelValue,
55
- };
56
- },
57
- watch: {
58
- model() {
59
- this.$emit('update:modelValue', this.model);
60
- },
61
- modelValue() {
62
- this.model = this.modelValue;
63
- },
64
- },
65
- methods: {
66
- selectItem(value) {
67
- this.model = value;
68
- },
69
- }
70
- }
71
- </script>
72
-
73
- <style>
74
- </style>
1
+ <template>
2
+ <el-input class="tnxel-input-dropdown"
3
+ v-model="model"
4
+ :disabled="disabled"
5
+ >
6
+ <template #append v-if="items?.length">
7
+ <el-dropdown :trigger="trigger" @command="selectItem">
8
+ <span class="el-dropdown-link">
9
+ <Icon value="ArrowDown"/>
10
+ </span>
11
+ <template #dropdown>
12
+ <el-dropdown-menu>
13
+ <template v-for="(item, index) of items" :key="index">
14
+ <el-dropdown-item
15
+ :command="item[valueName]"
16
+ :title="item[titleName]"
17
+ >
18
+ {{ item[textName] }}
19
+ </el-dropdown-item>
20
+ </template>
21
+ </el-dropdown-menu>
22
+ </template>
23
+ </el-dropdown>
24
+ </template>
25
+ </el-input>
26
+ </template>
27
+
28
+ <script>
29
+ import Icon from '../icon/Icon.vue';
30
+
31
+ export default {
32
+ name: 'TnxelInputDropdown',
33
+ components: {Icon},
34
+ props: {
35
+ modelValue: String,
36
+ items: Array,
37
+ valueName: {
38
+ type: String,
39
+ default: 'value',
40
+ },
41
+ textName: {
42
+ type: String,
43
+ default: 'text',
44
+ },
45
+ titleName: {
46
+ type: String,
47
+ default: 'title',
48
+ },
49
+ trigger: String,
50
+ disabled: Boolean,
51
+ },
52
+ data() {
53
+ return {
54
+ model: this.modelValue,
55
+ };
56
+ },
57
+ watch: {
58
+ model() {
59
+ this.$emit('update:modelValue', this.model);
60
+ },
61
+ modelValue() {
62
+ this.model = this.modelValue;
63
+ },
64
+ },
65
+ methods: {
66
+ selectItem(value) {
67
+ this.model = value;
68
+ },
69
+ }
70
+ }
71
+ </script>
72
+
73
+ <style>
74
+ </style>
@@ -246,7 +246,7 @@ export default {
246
246
  },
247
247
  currentText() {
248
248
  let item = this.getItem(this.model);
249
- return item ? item[this.textName] : undefined;
249
+ return item ? item[this.textName] : this.emptyText;
250
250
  },
251
251
  modelEqualsModelValue() {
252
252
  if (Array.isArray(this.model) && Array.isArray(this.modelValue)) {
@@ -1,15 +1,15 @@
1
- /**
2
- * 工具栏项
3
- */
4
- export default class ToolBarItem {
5
-
6
- icon = '';
7
- caption = '';
8
- disabled = false;
9
- click = null;
10
-
11
- static from(object) {
12
- return Object.assign(new ToolBarItem(), object);
13
- }
14
-
15
- }
1
+ /**
2
+ * 工具栏项
3
+ */
4
+ export default class ToolBarItem {
5
+
6
+ icon = '';
7
+ caption = '';
8
+ disabled = false;
9
+ click = null;
10
+
11
+ static from(object) {
12
+ return Object.assign(new ToolBarItem(), object);
13
+ }
14
+
15
+ }
@@ -1,56 +1,56 @@
1
- <template>
2
- <div class="tnxel-toolbar">
3
- <div class="flex-v-center">
4
- <template v-for="(item, index) of leftItems" :key="index">
5
- <el-divider direction="vertical" v-if="item === '|'"/>
6
- <ToolBarItem
7
- :item="Item.from(item)"
8
- :size="size"
9
- v-else-if="typeof item ==='object'"/>
10
- </template>
11
- </div>
12
- <div class="flex-v-center">
13
- <ToolBarItem v-for="(item, index) of rightItems" :key="index"
14
- :item="Item.from(item)"
15
- :size="size"/>
16
- </div>
17
- </div>
18
- </template>
19
-
20
- <script>
21
- import ToolBarItem from './ToolBarItem.vue';
22
- import Item from './ToolBarItem.js';
23
-
24
- export default {
25
- name: 'TnxelToolbar',
26
- components: {ToolBarItem},
27
- props: {
28
- leftItems: Array,
29
- rightItems: Array,
30
- size: {
31
- type: String,
32
- validator: value => {
33
- return ['small', 'default', 'large'].includes(value);
34
- },
35
- default: 'default',
36
- },
37
- },
38
- data() {
39
- return {
40
- Item,
41
- model: {},
42
- };
43
- },
44
- methods: {}
45
- }
46
- </script>
47
-
48
- <style>
49
- .tnxel-toolbar {
50
- height: 32px;
51
- padding: 0 8px;
52
- display: flex;
53
- align-items: center;
54
- justify-content: space-between;
55
- }
56
- </style>
1
+ <template>
2
+ <div class="tnxel-toolbar">
3
+ <div class="flex-v-center">
4
+ <template v-for="(item, index) of leftItems" :key="index">
5
+ <el-divider direction="vertical" v-if="item === '|'"/>
6
+ <ToolBarItem
7
+ :item="Item.from(item)"
8
+ :size="size"
9
+ v-else-if="typeof item ==='object'"/>
10
+ </template>
11
+ </div>
12
+ <div class="flex-v-center">
13
+ <ToolBarItem v-for="(item, index) of rightItems" :key="index"
14
+ :item="Item.from(item)"
15
+ :size="size"/>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script>
21
+ import ToolBarItem from './ToolBarItem.vue';
22
+ import Item from './ToolBarItem.js';
23
+
24
+ export default {
25
+ name: 'TnxelToolbar',
26
+ components: {ToolBarItem},
27
+ props: {
28
+ leftItems: Array,
29
+ rightItems: Array,
30
+ size: {
31
+ type: String,
32
+ validator: value => {
33
+ return ['small', 'default', 'large'].includes(value);
34
+ },
35
+ default: 'default',
36
+ },
37
+ },
38
+ data() {
39
+ return {
40
+ Item,
41
+ model: {},
42
+ };
43
+ },
44
+ methods: {}
45
+ }
46
+ </script>
47
+
48
+ <style>
49
+ .tnxel-toolbar {
50
+ height: 32px;
51
+ padding: 0 8px;
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: space-between;
55
+ }
56
+ </style>
package/src/tnxvue.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * 基于Vue 3的扩展支持
4
4
  */
5
5
  import tnxcore from '@truenewx/tnxcore';
6
+ // import tnxcore from '../../core/src/tnxcore';
6
7
  import validator from './tnxvue-validator';
7
8
  import createRouter from './tnxvue-router';
8
9
  import Text from './text/Text.vue';