@truenewx/tnxvue3 2.6.0

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.
Files changed (60) hide show
  1. package/README.md +3 -0
  2. package/package.json +76 -0
  3. package/sample/App.vue +19 -0
  4. package/sample/main.js +11 -0
  5. package/sample/pages/index.vue +79 -0
  6. package/sample/pages/info.vue +28 -0
  7. package/sample/tnx.js +31 -0
  8. package/src/aj-captcha/Verify/VerifyPoints.vue +258 -0
  9. package/src/aj-captcha/Verify/VerifySlide.vue +379 -0
  10. package/src/aj-captcha/Verify.vue +375 -0
  11. package/src/aj-captcha/api/index.js +19 -0
  12. package/src/aj-captcha/utils/ase.js +11 -0
  13. package/src/aj-captcha/utils/util.js +35 -0
  14. package/src/ant-design/tnxad-theme.css +5 -0
  15. package/src/ant-design/tnxad.css +8 -0
  16. package/src/ant-design/tnxad.js +23 -0
  17. package/src/element-plus/alert/Alert.vue +112 -0
  18. package/src/element-plus/avatar/Avatar.vue +124 -0
  19. package/src/element-plus/button/Button.vue +184 -0
  20. package/src/element-plus/check-icon/CheckIcon.vue +61 -0
  21. package/src/element-plus/close-error-button/CloseErrorButton.vue +45 -0
  22. package/src/element-plus/curd/Curd.vue +224 -0
  23. package/src/element-plus/date-picker/DatePicker.vue +206 -0
  24. package/src/element-plus/date-range/DateRange.vue +78 -0
  25. package/src/element-plus/datetime-picker/DateTimePicker.vue +129 -0
  26. package/src/element-plus/detail-form/DetailForm.vue +88 -0
  27. package/src/element-plus/dialog/Dialog.vue +259 -0
  28. package/src/element-plus/dialog/DialogContent.vue +13 -0
  29. package/src/element-plus/drawer/Drawer.vue +175 -0
  30. package/src/element-plus/dropdown-item/DropdownItem.vue +30 -0
  31. package/src/element-plus/enum-select/EnumSelect.vue +125 -0
  32. package/src/element-plus/fetch-cascader/FetchCascader.vue +138 -0
  33. package/src/element-plus/fetch-select/FetchSelect.vue +166 -0
  34. package/src/element-plus/fetch-tags/FetchTags.vue +122 -0
  35. package/src/element-plus/fss-upload/FssUpload.vue +306 -0
  36. package/src/element-plus/fss-view/FssView.vue +163 -0
  37. package/src/element-plus/icon/Icon.vue +221 -0
  38. package/src/element-plus/input-number/InputNumber.vue +150 -0
  39. package/src/element-plus/paged/Paged.vue +76 -0
  40. package/src/element-plus/permission-tree/PermissionTree.vue +184 -0
  41. package/src/element-plus/query-form/QueryForm.vue +138 -0
  42. package/src/element-plus/query-table/QueryTable.vue +402 -0
  43. package/src/element-plus/region-cascader/RegionCascader.vue +108 -0
  44. package/src/element-plus/select/Select.vue +446 -0
  45. package/src/element-plus/slider/Slider.vue +88 -0
  46. package/src/element-plus/steps-nav/StepsNav.vue +57 -0
  47. package/src/element-plus/submit-form/SubmitForm.vue +236 -0
  48. package/src/element-plus/table-column/TableColumn.vue +32 -0
  49. package/src/element-plus/tabs/Tabs.vue +93 -0
  50. package/src/element-plus/tnxel.css +890 -0
  51. package/src/element-plus/tnxel.js +528 -0
  52. package/src/element-plus/transfer/Transfer.vue +117 -0
  53. package/src/element-plus/upload/Upload.vue +856 -0
  54. package/src/percent/Percent.vue +12 -0
  55. package/src/text/Text.vue +33 -0
  56. package/src/tnxvue-cli.js +64 -0
  57. package/src/tnxvue-router.js +161 -0
  58. package/src/tnxvue-validator.js +365 -0
  59. package/src/tnxvue.css +12 -0
  60. package/src/tnxvue.js +343 -0
@@ -0,0 +1,446 @@
1
+ <template>
2
+ <el-checkbox-group class="tnxel-select tnxel-checkbox-group" v-model="model" :theme="theme" :size="size"
3
+ :disabled="disabled" v-if="selector === 'checkbox'">
4
+ <template v-if="items">
5
+ <el-checkbox v-for="item in items" :key="item[valueName]" :label="item[valueName]"
6
+ :data-value="item[valueName]" :title="item.title" :disabled="item.disabled">
7
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
8
+ <template v-else>
9
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
10
+ <span>{{ item[textName] }}</span>
11
+ </template>
12
+ </el-checkbox>
13
+ <template v-if="items.length === 0">
14
+ <slot name="empty" v-if="$slots.empty"></slot>
15
+ <span class="text-secondary" :class="emptyClass" v-else-if="emptyText">{{ emptyText }}</span>
16
+ </template>
17
+ </template>
18
+ <tnxel-icon value="Loading" v-else/>
19
+ </el-checkbox-group>
20
+ <div class="tnxel-select tnxel-tag-group d-flex flex-wrap" v-else-if="selector === 'tag' || selector === 'tags'">
21
+ <template v-if="items">
22
+ <el-button link :size="size" :class="emptyClass" v-if="emptyText" @click="clear">
23
+ {{ emptyText }}
24
+ </el-button>
25
+ <el-tag v-for="item in items" :key="item[valueName]" :type="theme"
26
+ :effect="isSelected(item[valueName]) ? 'dark' : 'plain'" :data-value="item[valueName]"
27
+ :title="item.title" :disabled="item.disabled" @click="select(item[valueName])">
28
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
29
+ <template v-else>
30
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
31
+ <span>{{ item[textName] }}</span>
32
+ </template>
33
+ </el-tag>
34
+ <template v-if="items.length === 0">
35
+ <slot name="empty"></slot>
36
+ </template>
37
+ </template>
38
+ <tnxel-icon value="Loading" v-else/>
39
+ </div>
40
+ <div class="tnxel-select tnxel-text-button-group d-flex flex-wrap"
41
+ v-else-if="selector === 'text' || selector === 'texts'">
42
+ <template v-if="items">
43
+ <el-button link :size="size" :class="emptyClass" v-if="emptyText" @click="clear">
44
+ {{ emptyText }}
45
+ </el-button>
46
+ <el-button v-for="item in items" :key="item[valueName]"
47
+ :link="!isSelected(item[valueName])" :plain="isSelected(item[valueName])"
48
+ :size="size" :data-value="item[valueName]" :title="item.title" :disabled="item.disabled"
49
+ @click="select(item[valueName], $event)">
50
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
51
+ <template v-else>
52
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
53
+ <span>{{ item[textName] }}</span>
54
+ </template>
55
+ </el-button>
56
+ <template v-if="items.length === 0">
57
+ <slot name="empty"></slot>
58
+ </template>
59
+ </template>
60
+ <tnxel-icon value="Loading" v-else/>
61
+ </div>
62
+ <el-radio-group class="tnxel-select tnxel-radio-group ignore-feedback" v-model="model" :theme="theme" :size="size"
63
+ :disabled="disabled" v-else-if="selector === 'radio'">
64
+ <el-radio :label="emptyValue" :class="emptyClass" :border="border" v-if="empty">{{ emptyText }}</el-radio>
65
+ <el-radio v-for="item in items" :key="item[valueName]" :label="item[valueName]" :data-value="item[valueName]"
66
+ :title="item.title" :disabled="item.disabled" :border="border">
67
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
68
+ <template v-else>
69
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
70
+ <span>{{ item[textName] }}</span>
71
+ </template>
72
+ </el-radio>
73
+ </el-radio-group>
74
+ <el-radio-group class="tnxel-select tnxel-radio-group ignore-feedback" v-model="model" :theme="theme" :size="size"
75
+ :disabled="disabled" v-else-if="selector === 'radio-button'">
76
+ <el-radio-button :class="emptyClass" :label="emptyValue" v-if="empty">{{ emptyText }}</el-radio-button>
77
+ <el-radio-button v-for="item in items" :key="item[valueName]" :label="item[valueName]"
78
+ :data-value="item[valueName]" :title="item.title" :disabled="item.disabled">
79
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
80
+ <template v-else>
81
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
82
+ <span>{{ item[textName] }}</span>
83
+ </template>
84
+ </el-radio-button>
85
+ </el-radio-group>
86
+ <el-dropdown class="tnxel-select tnxel-dropdown" trigger="click" :size="size" @command="onDropdownCommand"
87
+ v-else-if="selector === 'dropdown'">
88
+ <el-button style="width: 100%" :type="theme">
89
+ <div class="d-flex justify-content-between">
90
+ <span>{{ currentText }}</span>
91
+ <tnxel-icon value="ArrowDown"/>
92
+ </div>
93
+ </el-button>
94
+ <template #dropdown v-if="items && items.length">
95
+ <el-dropdown-menu>
96
+ <el-dropdown-item v-for="item in items" :key="item[valueName]" :command="item[valueName]"
97
+ :data-value="item[valueName]" :title="item.title" :disabled="item.disabled">
98
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
99
+ <template v-else>
100
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
101
+ <span>{{ item[textName] }}</span>
102
+ </template>
103
+ </el-dropdown-item>
104
+ </el-dropdown-menu>
105
+ </template>
106
+ </el-dropdown>
107
+ <el-dropdown class="tnxel-select tnxel-split-dropdown" :type="theme" :size="size" trigger="click" split-button
108
+ @command="onDropdownCommand" v-else-if="selector === 'split-dropdown'">
109
+ <span>{{ currentText }}</span>
110
+ <template #dropdown v-if="items && items.length">
111
+ <el-dropdown-menu>
112
+ <el-dropdown-item v-for="item in items" :key="item[valueName]" :command="item[valueName]"
113
+ :data-value="item[valueName]" :title="item.title" :disabled="item.disabled">
114
+ <slot name="option" :item="item" v-if="$slots.option"></slot>
115
+ <template v-else>
116
+ <i :class="item[iconName]" v-if="item[iconName]"></i>
117
+ <span>{{ item[textName] }}</span>
118
+ </template>
119
+ </el-dropdown-item>
120
+ </el-dropdown-menu>
121
+ </template>
122
+ </el-dropdown>
123
+ <el-select class="tnxel-select ignore-feedback" v-model="model" :placeholder="placeholder" :theme="theme"
124
+ :size="size" :disabled="disabled" :filterable="filterable" :filter-method="filter" v-else>
125
+ <el-option class="text-secondary" :value="emptyValue" :label="emptyText" :class="emptyClass" v-if="empty"/>
126
+ <template v-for="item in items">
127
+ <el-option :key="item[valueName]" :value="item[valueName]" :label="item[textName]"
128
+ :data-value="item[valueName]" :title="item.title" :disabled="item.disabled"
129
+ v-if="!hiddenValues.contains(item[valueName])">
130
+ <slot name="option" :item="item"></slot>
131
+ </el-option>
132
+ </template>
133
+ </el-select>
134
+ </template>
135
+
136
+ <script>
137
+ import Icon from '../icon/Icon.vue';
138
+
139
+ export const isMultiSelector = function (selector) {
140
+ return selector === 'checkbox' || selector === 'tags' || selector === 'texts';
141
+ }
142
+ export default {
143
+ name: 'TnxelSelect',
144
+ components: {
145
+ 'tnxel-icon': Icon,
146
+ },
147
+ props: {
148
+ id: [Number, String],
149
+ modelValue: {
150
+ type: [String, Number, Boolean, Array],
151
+ default: null,
152
+ },
153
+ selector: String,
154
+ items: Array,
155
+ valueName: {
156
+ type: String,
157
+ default: 'value',
158
+ },
159
+ textName: {
160
+ type: String,
161
+ default: 'text',
162
+ },
163
+ indexName: {
164
+ type: String,
165
+ default: 'index',
166
+ },
167
+ iconName: {
168
+ type: String,
169
+ default: 'icon',
170
+ },
171
+ defaultValue: {
172
+ type: [String, Number, Boolean, Array],
173
+ default: null,
174
+ },
175
+ empty: {
176
+ type: [Boolean, String],
177
+ default: false,
178
+ },
179
+ emptyValue: {
180
+ type: [String, Number, Boolean, Array],
181
+ default: null,
182
+ },
183
+ emptyClass: String,
184
+ placeholder: String,
185
+ disabled: Boolean,
186
+ tagClick: Function, // 点击一个标签选项时调用,如果返回false,则选项不会被选中
187
+ change: Function, // 选中值变化后的事件处理函数,由于比element的change事件传递更多参数,所以以prop的形式指定,以尽量节省性能
188
+ filterable: Boolean,
189
+ theme: String,
190
+ size: String,
191
+ border: Boolean,
192
+ },
193
+ emits: ['update:modelValue'],
194
+ data() {
195
+ let model = this.getModel(this.items);
196
+ if (model !== this.modelValue) {
197
+ this.$emit('update:modelValue', model);
198
+ }
199
+ return {
200
+ model: model,
201
+ hiddenValues: [],
202
+ };
203
+ },
204
+ computed: {
205
+ emptyText() {
206
+ return typeof this.empty === 'string' ? this.empty : '';
207
+ },
208
+ currentText() {
209
+ let item = this.getItem(this.model);
210
+ return item ? item[this.textName] : undefined;
211
+ },
212
+ },
213
+ watch: {
214
+ model(newValue, oldValue) {
215
+ this.$emit('update:modelValue', newValue);
216
+ // 新旧值不同时为空才触发变更事件
217
+ const util = window.tnx.util;
218
+ if (util.object.isNotEmpty(newValue) || util.object.isNotEmpty(oldValue)) {
219
+ let vm = this;
220
+ // 确保变更事件在值变更应用后再触发
221
+ this.$nextTick(function () {
222
+ vm.triggerChange(newValue);
223
+ });
224
+ }
225
+ },
226
+ modelValue() {
227
+ this.model = this.getModel(this.items);
228
+ },
229
+ items(items) {
230
+ this.model = this.getModel(items);
231
+ },
232
+ },
233
+ methods: {
234
+ isMulti() {
235
+ return isMultiSelector(this.selector);
236
+ },
237
+ triggerChange(value) {
238
+ if (this.change) {
239
+ let item = undefined;
240
+ if (this.isMulti()) {
241
+ item = [];
242
+ if (Array.isArray(value)) {
243
+ for (let v of value) {
244
+ item.push(this.getItem(v));
245
+ }
246
+ }
247
+ } else {
248
+ item = this.getItem(value);
249
+ }
250
+ this.change(item, this.id);
251
+ }
252
+ },
253
+ getItem(value) {
254
+ if (this.empty && value === this.emptyValue) {
255
+ let item = {};
256
+ item[this.valueName] = this.emptyValue;
257
+ item[this.textName] = this.emptyText;
258
+ return item;
259
+ }
260
+ if (value !== undefined && value !== null && this.items) {
261
+ for (let item of this.items) {
262
+ if ((item[this.valueName] + '') === (value + '')) {
263
+ return item;
264
+ }
265
+ }
266
+ }
267
+ return undefined;
268
+ },
269
+ getText(value) {
270
+ let item = this.getItem(value);
271
+ return item ? item[this.textName] : undefined;
272
+ },
273
+ getModel(items) {
274
+ const util = window.tnx.util;
275
+ let model = this.modelValue;
276
+ if (util.object.isNull(model)) {
277
+ model = this.defaultValue;
278
+ }
279
+ if (this.isMulti()) { // 多选时需确保值为数组
280
+ if (util.object.isNull(model)) {
281
+ return [];
282
+ }
283
+ if (!Array.isArray(model)) {
284
+ model = [model];
285
+ }
286
+ return model;
287
+ }
288
+ if (util.object.isNull(model)) {
289
+ return null;
290
+ }
291
+ if (items?.length) {
292
+ let item = this.getItem(model);
293
+ if (item) {
294
+ return item[this.valueName];
295
+ } else { // 如果当前值找不到匹配的选项,则需要考虑是设置为空还是默认选项
296
+ if (!this.empty) { // 如果不能为空,则默认选中第一个选项
297
+ let firstItem = items[0];
298
+ if (firstItem[this.valueName]) {
299
+ return firstItem[this.valueName];
300
+ }
301
+ }
302
+ }
303
+ }
304
+ return model;
305
+ },
306
+ filter(keyword) {
307
+ for (let item of this.items) {
308
+ let itemValue = item[this.valueName];
309
+ let hiddenIndex = this.hiddenValues.indexOf(itemValue);
310
+ if (this.matchesItem(item, keyword)) {
311
+ if (hiddenIndex >= 0) { // 匹配且原本已隐藏的,则取消隐藏
312
+ this.hiddenValues.splice(hiddenIndex, 1);
313
+ }
314
+ } else {
315
+ if (hiddenIndex < 0) { // 不匹配且原本未隐藏的,则进行隐藏
316
+ this.hiddenValues.push(itemValue);
317
+ }
318
+ }
319
+ }
320
+ },
321
+ matchesItem(item, keyword) {
322
+ return !keyword || window.tnx.util.string.matchesForEach(item[this.valueName], keyword)
323
+ || window.tnx.util.string.matchesForEach(item[this.textName], keyword)
324
+ || window.tnx.util.string.matchesForEach(item[this.indexName], keyword)
325
+ },
326
+ onDropdownCommand(value) {
327
+ this.model = value;
328
+ },
329
+ isSelected(value) {
330
+ if (Array.isArray(this.model)) {
331
+ return this.model.contains(value);
332
+ } else {
333
+ return this.model === value;
334
+ }
335
+ },
336
+ select(value, event) {
337
+ if (this.tagClick) {
338
+ let item = this.getItem(value);
339
+ if (item) {
340
+ if (this.tagClick(item) === false) {
341
+ return;
342
+ }
343
+ }
344
+ }
345
+ if (this.isMulti()) {
346
+ let index = this.model.indexOf(value);
347
+ if (index >= 0) {
348
+ this.model = this.model.filter(function (e, i) {
349
+ return i !== index;
350
+ });
351
+ } else {
352
+ this.model = this.model.concat([value]);
353
+ }
354
+ } else {
355
+ this.model = value;
356
+ }
357
+ if (event) {
358
+ event.currentTarget.blur();
359
+ }
360
+ },
361
+ clear() {
362
+ if (this.isMulti()) {
363
+ this.model = [];
364
+ } else {
365
+ this.model = null;
366
+ }
367
+ },
368
+ }
369
+ }
370
+ </script>
371
+
372
+ <style>
373
+ .tnxel-tag-group .el-tag {
374
+ margin-top: 5px;
375
+ margin-bottom: 5px;
376
+ cursor: pointer;
377
+ }
378
+
379
+ .tnxel-tag-group .el-tag:not(:last-child) {
380
+ margin-right: 10px;
381
+ }
382
+
383
+ .tnxel-tag-group .el-button,
384
+ .tnxel-text-button-group .el-button {
385
+ padding: 0.5rem 0.75rem;
386
+ margin: 2px 0.5rem 2px 0;
387
+ }
388
+
389
+ .tnxel-tag-group .el-button__text--expand,
390
+ .tnxel-text-button-group .el-button__text--expand {
391
+ letter-spacing: unset;
392
+ margin-right: unset;
393
+ }
394
+
395
+ .tnxel-tag-group .el-button.is-link,
396
+ .tnxel-text-button-group .el-button.is-link {
397
+ color: unset;
398
+ }
399
+
400
+ .tnxel-tag-group .el-button.is-link:hover,
401
+ .tnxel-text-button-group .el-button.is-link:hover {
402
+ color: var(--el-color-primary);
403
+ border-color: transparent;
404
+ }
405
+
406
+ .tnxel-text-button-group .el-button.is-plain {
407
+ border-color: var(--el-color-primary);
408
+ color: var(--el-color-primary);
409
+ }
410
+
411
+ .tnxel-text-button-group .el-button + .el-button {
412
+ margin-left: 0;
413
+ }
414
+
415
+ .tnxel-radio-group[theme="error"] .el-radio-button__original-radio:checked + .el-radio-button__inner,
416
+ .tnxel-radio-group[theme="danger"] .el-radio-button__original-radio:checked + .el-radio-button__inner {
417
+ background-color: var(--el-color-danger);
418
+ border-color: var(--el-color-danger);
419
+ box-shadow: -1px 0 0 0 var(--el-color-danger);
420
+ }
421
+
422
+ .tnxel-radio-group[theme="error"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover,
423
+ .tnxel-radio-group[theme="danger"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover {
424
+ color: var(--el-color-danger);
425
+ }
426
+
427
+ .tnxel-radio-group[theme="warning"] .el-radio-button__original-radio:checked + .el-radio-button__inner {
428
+ background-color: var(--el-color-warning);
429
+ border-color: var(--el-color-warning);
430
+ box-shadow: -1px 0 0 0 var(--el-color-warning);
431
+ }
432
+
433
+ .tnxel-radio-group[theme="warning"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover {
434
+ color: var(--el-color-warning);
435
+ }
436
+
437
+ .tnxel-radio-group[theme="success"] .el-radio-button__original-radio:checked + .el-radio-button__inner {
438
+ background-color: var(--el-color-success);
439
+ border-color: var(--el-color-success);
440
+ box-shadow: -1px 0 0 0 var(--el-color-success);
441
+ }
442
+
443
+ .tnxel-radio-group[theme="success"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover {
444
+ color: var(--el-color-success);
445
+ }
446
+ </style>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <div class="tnxel-slider">
3
+ <el-slider class="flex-grow-1" v-model="index" :max="max" :format-tooltip="format" :disabled="disabled"
4
+ show-stops @input="input" @change="change"/>
5
+ <div class="ms-3 text-center text-regular" :style="{visibility: captionVisibility, width: captionMaxWidth}">
6
+ {{ caption }}
7
+ </div>
8
+ </div>
9
+ </template>
10
+
11
+ <script>
12
+
13
+ export default {
14
+ name: 'TnxelSlider',
15
+ props: {
16
+ modelValue: Number,
17
+ items: Array,
18
+ itemUnit: {
19
+ type: String,
20
+ default: '',
21
+ },
22
+ disabled: Boolean,
23
+ },
24
+ emits: ['update:modelValue'],
25
+ data() {
26
+ let index = this.indexOf(this.modelValue);
27
+ if (index < 0) {
28
+ index = 0;
29
+ }
30
+ return {
31
+ index: index,
32
+ captionVisibility: this.disabled ? 'hidden' : 'visible',
33
+ }
34
+ },
35
+ computed: {
36
+ max() {
37
+ return this.items.length - 1;
38
+ },
39
+ caption() {
40
+ return this.format(this.index);
41
+ },
42
+ captionMaxWidth() {
43
+ let maxLength = 0;
44
+ for (let i = 0; i < this.items.length; i++) {
45
+ let caption = this.format(i);
46
+ if (caption.length > maxLength) {
47
+ maxLength = caption.length;
48
+ }
49
+ }
50
+ return (maxLength * 14) + 'px';
51
+ },
52
+ },
53
+ watch: {
54
+ modelValue(value) {
55
+ return this.indexOf(value);
56
+ },
57
+ index(index) {
58
+ let value = this.items[index];
59
+ this.$emit('update:modelValue', value);
60
+ },
61
+ disabled() {
62
+ this.captionVisibility = this.disabled ? 'hidden' : 'visible';
63
+ },
64
+ },
65
+ methods: {
66
+ indexOf(value) {
67
+ return this.items.indexOf(value);
68
+ },
69
+ format(index) {
70
+ return this.items[index] + this.itemUnit;
71
+ },
72
+ input(index) {
73
+ this.captionVisibility = 'hidden';
74
+ },
75
+ change(index) {
76
+ this.captionVisibility = 'visible';
77
+ }
78
+ }
79
+ }
80
+ </script>
81
+
82
+ <style>
83
+ .tnxel-slider {
84
+ display: flex;
85
+ align-items: center;
86
+ padding-left: 10px;
87
+ }
88
+ </style>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <el-steps class="w-fit-content tnxel-steps-nav" :id="id" direction="vertical" :space="space">
3
+ <el-step status="finish" v-for="item in items" :key="item.target" :data-target="item.target"
4
+ :title="item.title"/>
5
+ </el-steps>
6
+ </template>
7
+
8
+ <script>
9
+ import $ from 'jquery';
10
+
11
+ export default {
12
+ name: 'TnxelStepsNav',
13
+ props: {
14
+ /**
15
+ * 所属滚动容器的选择器
16
+ */
17
+ container: {
18
+ type: String,
19
+ default: () => 'main',
20
+ },
21
+ items: {
22
+ type: Array,
23
+ required: true,
24
+ },
25
+ base: [String, Object],
26
+ space: [String, Number],
27
+ },
28
+ data() {
29
+ return {
30
+ id: window.tnx.util.string.random(32),
31
+ activeIndex: 0,
32
+ topOffset: 0,
33
+ }
34
+ },
35
+ mounted() {
36
+ this.topOffset = $('#' + this.id).offset().top - $(this.container).offset().top - 16;
37
+ let vm = this;
38
+ $('#' + this.id + ' .el-step').each(function(index, step) {
39
+ $(step).click(function() {
40
+ vm.navTo(index);
41
+ });
42
+ });
43
+ },
44
+ methods: {
45
+ navTo(index) {
46
+ this.activeIndex = index;
47
+ let top = 0;
48
+ if (index >= 0) {
49
+ let $base = this.base ? $(this.base) : $('#' + this.items[0].target);
50
+ let $target = $('#' + this.items[index].target);
51
+ top = $target.offset().top - $base.offset().top + this.topOffset;
52
+ }
53
+ $(this.container).scrollTop(top);
54
+ },
55
+ }
56
+ }
57
+ </script>