SortSection.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. <script setup lang="ts">
  2. import { useI18n } from 'vue-i18n'
  3. import SortSvg from '~/assets/icons/sort.svg'
  4. import SortUpSvg from '~/assets/icons/sort-up.svg'
  5. import SortDownSvg from '~/assets/icons/sort-down.svg'
  6. defineProps<{
  7. onFindPartner: () => void
  8. }>()
  9. const emit = defineEmits<{
  10. (e: 'changeSort', payload: { sortByStar: number, sortByPrice: number }): void
  11. }>()
  12. type SortOption = {
  13. labelKey: string
  14. value: string
  15. }
  16. type SortDirection = 'none' | 'asc' | 'desc'
  17. const { t } = useI18n()
  18. // star -> sortByStar, price -> sortByPrice
  19. const sortOptions: SortOption[] = [
  20. { labelKey: 'home.sort.star', value: 'star' },
  21. { labelKey: 'home.sort.price', value: 'price' },
  22. ]
  23. const activeSort = ref<SortOption['value']>(sortOptions[0]?.value ?? '')
  24. const sortDirections = ref<Record<string, SortDirection>>({
  25. star: 'none',
  26. price: 'none',
  27. })
  28. const directionToBackend = (direction: SortDirection): number => {
  29. // 后端字段:-1 默认,0 升序,1 降序
  30. if (direction === 'asc')
  31. return 0
  32. if (direction === 'desc')
  33. return 1
  34. return -1
  35. }
  36. const emitSortChange = () => {
  37. const starDirection = sortDirections.value.star ?? 'none'
  38. const priceDirection = sortDirections.value.price ?? 'none'
  39. emit('changeSort', {
  40. sortByStar: directionToBackend(starDirection),
  41. sortByPrice: directionToBackend(priceDirection),
  42. })
  43. }
  44. // Handle sort click
  45. const handleSort = (sortValue: string) => {
  46. const currentDirection = sortDirections.value[sortValue] ?? 'none'
  47. let nextDirection: SortDirection
  48. // Cycle through: none -> desc -> asc -> none -> ...
  49. if (currentDirection === 'none')
  50. nextDirection = 'desc'
  51. else if (currentDirection === 'desc')
  52. nextDirection = 'asc'
  53. else
  54. nextDirection = 'none'
  55. // 支持多个排序字段同时生效:仅切换当前字段的排序方向,不重置其他字段
  56. sortDirections.value[sortValue] = nextDirection
  57. activeSort.value = sortValue
  58. emitSortChange()
  59. }
  60. // Get sort icon component based on direction
  61. const getSortIcon = (sortValue: string) => {
  62. const direction = sortDirections.value[sortValue]
  63. if (direction === 'asc')
  64. return SortUpSvg
  65. if (direction === 'desc')
  66. return SortDownSvg
  67. return SortSvg
  68. }
  69. </script>
  70. <template>
  71. <section class="flex flex-wrap items-center justify-between bg-bg-primary z-50">
  72. <div class="flex gap-4 text-xs font-normal text-text-secondary">
  73. <button
  74. v-for="sortOption in sortOptions"
  75. :key="sortOption.value"
  76. class="flex items-center gap-1 py-1"
  77. :class="sortDirections[sortOption.value] !== 'none' ? 'text-text-primary' : ''"
  78. @click="handleSort(sortOption.value)"
  79. >
  80. {{ t(sortOption.labelKey) }}
  81. <component :is="getSortIcon(sortOption.value)" />
  82. </button>
  83. </div>
  84. <van-button
  85. round
  86. size="small"
  87. class="find-btn flex items-center justify-center"
  88. plain
  89. type="primary"
  90. @click="onFindPartner"
  91. >
  92. <span class="text-xs font-title font-semibold text-text-secondary">{{ t('home.findPartner.title') }}</span>
  93. <van-icon
  94. class="ml-1 text-text-secondary"
  95. name="arrow"
  96. size="14"
  97. />
  98. </van-button>
  99. </section>
  100. </template>
  101. <style lang="scss" scoped>
  102. .find-btn {
  103. height: 40px;
  104. padding: 0 12px;
  105. border-radius: 20px;
  106. border: 1px solid #FFF;
  107. background: linear-gradient(90deg, rgba(183, 238, 255, 0.60) 0.01%, rgba(221, 255, 183, 0.60) 26.99%, rgba(255, 255, 255, 0.60) 75.07%);
  108. }
  109. </style>