wd-segmented.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <template>
  2. <view :class="`wd-segmented ${customClass}`" :style="customStyle">
  3. <view
  4. :class="`wd-segmented__item is-${size} ${state.activeIndex === index ? 'is-active' : ''} ${
  5. disabled || (isObj(option) ? option.disabled : false) ? 'is-disabled' : ''
  6. }`"
  7. @click="handleClick(option, index)"
  8. v-for="(option, index) in options"
  9. :key="index"
  10. >
  11. <view class="wd-segmented__item-label">
  12. <slot name="label" v-if="$slots.label" :option="isObj(option) ? option : { value: option }"></slot>
  13. <template v-else>
  14. {{ isObj(option) ? option.value : option }}
  15. </template>
  16. </view>
  17. </view>
  18. <view :class="`wd-segmented__item--active ${activeDisabled ? 'is-disabled' : ''}`" :style="state.activeStyle"></view>
  19. </view>
  20. </template>
  21. <script lang="ts">
  22. export default {
  23. name: 'wd-segmented',
  24. options: {
  25. addGlobalClass: true,
  26. virtualHost: true,
  27. styleIsolation: 'shared'
  28. }
  29. }
  30. </script>
  31. <script setup lang="ts">
  32. import { computed, getCurrentInstance, onMounted, reactive, watch } from 'vue'
  33. import { getRect, isObj, objToStyle, addUnit, pause, isEqual } from '../common/util'
  34. import type { CSSProperties } from 'vue'
  35. import { segmentedProps, type SegmentedExpose, type SegmentedOption } from './types'
  36. const $item = '.wd-segmented__item'
  37. const props = defineProps(segmentedProps)
  38. const emit = defineEmits(['update:value', 'change', 'click'])
  39. const state = reactive({
  40. activeIndex: 0, // 选中项
  41. activeStyle: '' // 选中样式
  42. })
  43. const activeDisabled = computed(() => {
  44. return props.disabled || (props.options[0] && isObj(props.options[0]) ? props.options[0].disabled : false)
  45. })
  46. watch(
  47. () => props.value,
  48. () => {
  49. updateCurrentIndex()
  50. updateActiveStyle()
  51. if (props.vibrateShort) {
  52. uni.vibrateShort({})
  53. }
  54. },
  55. {
  56. immediate: false
  57. }
  58. )
  59. const { proxy } = getCurrentInstance() as any
  60. onMounted(async () => {
  61. updateCurrentIndex()
  62. await pause()
  63. updateActiveStyle(false)
  64. })
  65. /**
  66. * 更新滑块偏移量
  67. *
  68. */
  69. function updateActiveStyle(animation: boolean = true) {
  70. getRect($item, true, proxy).then((rects) => {
  71. const rect = rects[state.activeIndex]
  72. const style: CSSProperties = {
  73. position: 'absolute',
  74. width: addUnit(rect.width!),
  75. 'z-index': 0
  76. }
  77. const left = rects.slice(0, state.activeIndex).reduce((prev, curr) => prev + Number(curr.width), 0)
  78. if (left) {
  79. style.transform = `translateX(${left}px)`
  80. }
  81. if (animation) {
  82. style.transition = 'all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)'
  83. }
  84. state.activeStyle = objToStyle(style)
  85. })
  86. }
  87. /**
  88. * 更新值
  89. */
  90. function updateValue(newValue: string | number, option: string | number | SegmentedOption) {
  91. if (!isEqual(newValue, props.value)) {
  92. emit('update:value', newValue)
  93. emit('change', isObj(option) ? option : { value: newValue })
  94. }
  95. }
  96. /**
  97. * 更新当前下标
  98. */
  99. function updateCurrentIndex() {
  100. const index = props.options.findIndex((option: string | number | SegmentedOption) => {
  101. const value = isObj(option) ? option.value : option
  102. return isEqual(value, props.value)
  103. })
  104. if (index >= 0) {
  105. state.activeIndex = index
  106. } else {
  107. const value = isObj(props.options[0]) ? props.options[0].value : props.options[0]
  108. updateValue(value, props.options[0])
  109. }
  110. }
  111. function handleClick(option: string | number | SegmentedOption, index: number) {
  112. const disabled = props.disabled || (isObj(option) ? option.disabled : false)
  113. if (disabled) {
  114. return
  115. }
  116. const value = isObj(option) ? option.value : option
  117. state.activeIndex = index
  118. updateActiveStyle()
  119. updateValue(value, option)
  120. emit('click', isObj(option) ? option : { value })
  121. }
  122. defineExpose<SegmentedExpose>({
  123. updateActiveStyle
  124. })
  125. </script>
  126. <style lang="scss" scoped>
  127. @import './index.scss';
  128. </style>