tabBar.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <template>
  2. <view class="display-flex__ac channel-type__selector">
  3. <view
  4. v-for="(item, index) in list"
  5. :key="index"
  6. class="channel-type__tag text-center font-bold"
  7. :class="{ active: sltVal == item.value }"
  8. @click="tabClick(item)"
  9. >
  10. {{ item.name }}
  11. </view>
  12. <view
  13. class="channel-type__block backgroundCustom"
  14. :style="{
  15. width: blockWidth,
  16. transform: `translateX(${blockTranslateX})`
  17. }"
  18. ></view>
  19. </view>
  20. </template>
  21. <script setup lang="ts">
  22. import { computed, toRefs } from 'vue';
  23. const props = defineProps({
  24. config: {
  25. type: Object,
  26. default: () => ({
  27. name: "name",
  28. value: "value",
  29. })
  30. },
  31. tabList: {
  32. type: Array,
  33. default: () => []
  34. },
  35. value: {
  36. type: [String, Number],
  37. default: ""
  38. }
  39. });
  40. const emit = defineEmits(['tabClick', 'update:value']);
  41. // Use props directly or computed to normalize list based on config if needed.
  42. // However, the original code assigned `tabList` to `list` data.
  43. // And it accessed `item.value` and `item.name` in the template directly,
  44. // but the JS `tabClick` method used `this.properties.config.value` to find index.
  45. // The WXML used `item.value` directly in `data-value` and `sltVal == item.value`.
  46. // This implies the input `tabList` objects MUST have 'value' and 'name' properties,
  47. // OR the original WXML was relying on the fact that `item.value` worked.
  48. // Wait, looking at WXML: `{{item.name}}` and `data-value="{{item.value}}"`.
  49. // But `config` prop defaults to `{name: "name", value: "value"}`.
  50. // If the user passes a config with different keys, the WXML `item.name` might fail if not handled?
  51. // Actually, in the original JS `observer`, it just sets `list: val`.
  52. // So the original WXML assumes `item.name` and `item.value` exist on the objects in `tabList`.
  53. // BUT, the `config` prop suggests dynamic keys.
  54. // If `config` is used, we should probably map the list to standard keys or use the config keys in template.
  55. // The original WXML: `<view ...>{{item.name}}</view>`
  56. // This looks like it ignores `config.name` in the template rendering?
  57. // Let's check `tabClick` in JS:
  58. // `index: list.findIndex((item) => item[this.properties.config.value] == value)`
  59. // So logic USES config, but template uses hardcoded `name` and `value`.
  60. // This might be a bug or limitation in the original code, OR the `tabList` passed in always has `name`/`value` OR the `config` is rarely changed from default.
  61. // However, to be safe and "strict", I should probably respect `config` if possible, OR just reproduce the original behavior.
  62. // The original behavior: Template uses `.name` and `.value`. Logic uses `config`.
  63. // If I change template to use `item[config.name]`, it might break if `config` isn't passed correctly but data has `name`.
  64. // I will reproduce the original behavior:
  65. // 1. Template uses `item.name` and `item.value` (assuming data structure matches).
  66. // 2. Logic uses `config.value` for finding index.
  67. // Actually, looking at `tabClick` in JS: `const { value } = e.currentTarget.dataset;` -> comes from `data-value="{{item.value}}"`.
  68. // So `value` is definitely taken from `.value` property of item.
  69. // Then `list.findIndex((item) => item[this.properties.config.value] == value)`.
  70. // If `config.value` is "id", then it looks for `item.id == item.value`.
  71. // This implies `item.value` MUST be the value we are looking for.
  72. // I'll stick to the original WXML structure which accesses `.name` and `.value`.
  73. const { tabList: list, value: sltVal } = toRefs(props);
  74. const blockWidth = computed(() => {
  75. const len = list.value.length || 1;
  76. return `${100 / len}%`;
  77. });
  78. const blockTranslateX = computed(() => {
  79. const len = list.value.length || 1;
  80. const keyName = props.config.value || 'value'; // Default to 'value' if not set, though default prop handles it.
  81. const val = sltVal.value;
  82. if (len <= 0) return '100%';
  83. let idx = 0;
  84. for (let i = 0; i < len; i++) {
  85. // Original logic: list[i][keyName] == value
  86. // Note: original WXML passed `item.value` to dataset, so `value` is `item.value`.
  87. // But finding index uses `item[config.value]`.
  88. // This implies `item.value` and `item[config.value]` should be consistent.
  89. if (list.value[i][keyName] == val) {
  90. idx = i;
  91. break;
  92. }
  93. }
  94. // (idx * preWidth * (list.length || 1)) + '%'
  95. // preWidth = 100 / len
  96. // result = idx * (100/len) * len = idx * 100
  97. return `${idx * 100}%`;
  98. });
  99. const tabClick = (item: any) => {
  100. const val = item.value; // Corresponds to data-value="{{item.value}}"
  101. const keyName = props.config.value || 'value';
  102. const data = {
  103. config: props.config,
  104. list: list.value,
  105. value: val,
  106. index: list.value.findIndex((it: any) => it[keyName] == val),
  107. };
  108. emit('tabClick', data);
  109. emit('update:value', val);
  110. };
  111. </script>
  112. <style scoped lang="scss">
  113. .channel-type__selector {
  114. position: relative;
  115. height: 100upx;
  116. line-height: 100upx;
  117. overflow: hidden;
  118. display: flex;
  119. align-items: center;
  120. width: 100%;
  121. min-height: 80upx;
  122. height: fit-content;
  123. background-color: #fff;
  124. border-radius: 24upx;
  125. }
  126. .channel-type__tag {
  127. height: inherit;
  128. line-height: inherit;
  129. font-size: 32upx;
  130. flex: 1 0 0;
  131. z-index: 9;
  132. transition: all .3s ease-in-out;
  133. }
  134. .channel-type__tag.active {
  135. color: #fff;
  136. }
  137. .channel-type__block {
  138. position: absolute;
  139. height: 100%;
  140. z-index: 1;
  141. border-radius: 24upx;
  142. transition: all .3s ease-in-out;
  143. }
  144. .no-padding {
  145. padding: 0 !important;
  146. }
  147. .text-center {
  148. text-align: center;
  149. }
  150. .font-bold {
  151. font-weight: bold;
  152. }
  153. .backgroundCustom {
  154. background: var(--dominantColor) !important;
  155. color: #fff !important;
  156. }
  157. </style>