useFindPartnerPopup.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import { onUnmounted, reactive, readonly, watch } from 'vue'
  2. import { useBaseConstsStore } from '~/stores/baseConsts'
  3. import type { BaseConstsConfig } from '~/types/api'
  4. export interface FindPartnerTag {
  5. id: string
  6. /**
  7. * i18n key for the tag label
  8. */
  9. labelKey: string
  10. }
  11. export interface FindPartnerSection {
  12. id: 'gender' | 'age' | 'region' | 'rating' | 'price'
  13. /**
  14. * i18n key for the section title
  15. */
  16. titleKey: string
  17. tags: FindPartnerTag[]
  18. }
  19. export interface FindPartnerPopupState {
  20. visible: boolean
  21. sections: FindPartnerSection[]
  22. /** 每个 section 当前选中的 tag id,未选中则为 undefined */
  23. selected: Record<FindPartnerSection['id'], string | undefined>
  24. }
  25. export interface FindPartnerFilterPayload {
  26. /** 后端 gender 参数 */
  27. gender: number
  28. /** 后端 ageRange 参数 */
  29. ageRange: number
  30. /** 后端 areCode 参数 */
  31. areCode: string
  32. /** 后端 priceRange 参数 */
  33. priceRange: number
  34. /** 后端 sortByStar 参数(结合弹窗与外部排序) */
  35. sortByStar: number
  36. }
  37. const initialSelected: FindPartnerPopupState['selected'] = {
  38. gender: 'unlimited',
  39. age: 'unlimited',
  40. region: '',
  41. rating: 'unlimited',
  42. price: 'unlimited',
  43. }
  44. const state = reactive<FindPartnerPopupState>({
  45. visible: false,
  46. sections: [
  47. {
  48. id: 'gender',
  49. titleKey: 'home.findPartner.sections.gender.title',
  50. tags: [
  51. {
  52. id: 'unlimited',
  53. labelKey: 'home.findPartner.sections.gender.tags.unlimited',
  54. },
  55. {
  56. id: 'male',
  57. labelKey: 'home.findPartner.sections.gender.tags.male',
  58. },
  59. {
  60. id: 'female',
  61. labelKey: 'home.findPartner.sections.gender.tags.female',
  62. },
  63. ],
  64. },
  65. {
  66. id: 'age',
  67. titleKey: 'home.findPartner.sections.age.title',
  68. tags: [
  69. {
  70. id: 'unlimited',
  71. labelKey: 'home.findPartner.sections.age.tags.unlimited',
  72. },
  73. {
  74. id: '15-25',
  75. labelKey: 'home.findPartner.sections.age.tags.range_15_25',
  76. },
  77. {
  78. id: '25-35',
  79. labelKey: 'home.findPartner.sections.age.tags.range_25_35',
  80. },
  81. {
  82. id: '35+',
  83. labelKey: 'home.findPartner.sections.age.tags.range_35_plus',
  84. },
  85. ],
  86. },
  87. {
  88. id: 'region',
  89. titleKey: 'home.findPartner.sections.region.title',
  90. tags: [],
  91. },
  92. {
  93. id: 'rating',
  94. titleKey: 'home.findPartner.sections.rating.title',
  95. tags: [
  96. {
  97. id: 'unlimited',
  98. labelKey: 'home.findPartner.sections.rating.tags.unlimited',
  99. },
  100. {
  101. id: 'high-to-low',
  102. labelKey: 'home.findPartner.sections.rating.tags.highToLow',
  103. },
  104. {
  105. id: 'low-to-high',
  106. labelKey: 'home.findPartner.sections.rating.tags.lowToHigh',
  107. },
  108. ],
  109. },
  110. {
  111. id: 'price',
  112. titleKey: 'home.findPartner.sections.price.title',
  113. tags: [
  114. {
  115. id: 'unlimited',
  116. labelKey: 'home.findPartner.sections.price.tags.unlimited',
  117. },
  118. {
  119. id: '0-10',
  120. labelKey: 'home.findPartner.sections.price.tags.range_0_10',
  121. },
  122. {
  123. id: '10-15',
  124. labelKey: 'home.findPartner.sections.price.tags.range_10_15',
  125. },
  126. {
  127. id: '15-25',
  128. labelKey: 'home.findPartner.sections.price.tags.range_15_25',
  129. },
  130. {
  131. id: '25-50',
  132. labelKey: 'home.findPartner.sections.price.tags.range_25_50',
  133. },
  134. {
  135. id: 'over50',
  136. labelKey: 'home.findPartner.sections.price.tags.range_over50',
  137. },
  138. ],
  139. },
  140. ],
  141. selected: { ...initialSelected },
  142. })
  143. const open = () => {
  144. state.visible = true
  145. }
  146. const close = () => {
  147. state.visible = false
  148. }
  149. const toggleTag = (sectionId: FindPartnerSection['id'], tagId: string) => {
  150. const current = state.selected[sectionId]
  151. state.selected[sectionId] = current === tagId ? undefined : tagId
  152. }
  153. const resetFilters = () => {
  154. state.selected = {
  155. ...initialSelected,
  156. }
  157. }
  158. // ===== 弹窗选项 -> 列表接口参数映射 =====
  159. const mapGenderToBackend = (id?: string): number => {
  160. // 性别:-1:无限制,0:未知,1:男,2:女
  161. if (!id || id === 'unlimited')
  162. return -1
  163. if (id === 'male')
  164. return 1
  165. if (id === 'female')
  166. return 2
  167. return -1
  168. }
  169. const mapAgeToBackend = (id?: string): number => {
  170. // 年龄,-1:不限制,0:15-25,1:25-35,2:35以上
  171. if (!id || id === 'unlimited')
  172. return -1
  173. if (id === '15-25')
  174. return 0
  175. if (id === '25-35')
  176. return 1
  177. if (id === '35+')
  178. return 2
  179. return -1
  180. }
  181. const mapRegionToBackend = (id?: string): string => {
  182. // 区域编码,暂无“不限”选项;未选择时传空字符串
  183. if (!id)
  184. return ''
  185. return id
  186. }
  187. const mapPriceToBackend = (id?: string): number => {
  188. // 价格区间:-1:无限制,0:[0,10],1:(10,15],2:(15,25],3:(25,50],4:over50
  189. if (!id || id === 'unlimited')
  190. return -1
  191. if (id === '0-10')
  192. return 0
  193. if (id === '10-15')
  194. return 1
  195. if (id === '15-25')
  196. return 2
  197. if (id === '25-50')
  198. return 3
  199. if (id === 'over50')
  200. return 4
  201. return -1
  202. }
  203. const mapRatingToSortByStar = (id: string | undefined, currentSortByStar: number): number => {
  204. // 评分排序,-1:默认排序,0:升序,1:降序
  205. if (!id || id === 'unlimited')
  206. return currentSortByStar
  207. if (id === 'high-to-low')
  208. return 1
  209. if (id === 'low-to-high')
  210. return 0
  211. return currentSortByStar
  212. }
  213. const getFilters = (currentSortByStar: number): FindPartnerFilterPayload => {
  214. const gender = mapGenderToBackend(state.selected.gender)
  215. const ageRange = mapAgeToBackend(state.selected.age)
  216. const areCode = mapRegionToBackend(state.selected.region)
  217. const priceRange = mapPriceToBackend(state.selected.price)
  218. const sortByStar = mapRatingToSortByStar(state.selected.rating, currentSortByStar)
  219. return {
  220. gender,
  221. ageRange,
  222. areCode,
  223. priceRange,
  224. sortByStar,
  225. }
  226. }
  227. type ApplyListener = () => void
  228. const applyListeners = new Set<ApplyListener>()
  229. const onApply = (listener: ApplyListener) => {
  230. applyListeners.add(listener)
  231. onUnmounted(() => {
  232. applyListeners.delete(listener)
  233. })
  234. }
  235. const applyFilters = () => {
  236. applyListeners.forEach(listener => listener())
  237. }
  238. const regionTagsInitializedFromBaseConsts = ref(false)
  239. const setupRegionTagsFromBaseConsts = () => {
  240. if (regionTagsInitializedFromBaseConsts.value)
  241. return
  242. const baseConstsStore = useBaseConstsStore()
  243. const updateRegionTags = (config: BaseConstsConfig | null) => {
  244. const regionSection = state.sections.find(section => section.id === 'region')
  245. if (!regionSection)
  246. return
  247. const list = config?.commonAreaConsts
  248. if (!Array.isArray(list) || list.length === 0)
  249. return
  250. regionSection.tags = list
  251. .filter(item => item && item.code)
  252. .map(item => ({
  253. // areCode 直接取后端提供的区域 code
  254. id: String(item.code),
  255. // 这里直接使用后端下发的 name,i18n 未配置时会直接展示该文案
  256. labelKey: String(item.name),
  257. }))
  258. regionTagsInitializedFromBaseConsts.value = true
  259. }
  260. watch(
  261. () => baseConstsStore.config,
  262. (config) => {
  263. updateRegionTags(config)
  264. },
  265. {
  266. immediate: true,
  267. },
  268. )
  269. }
  270. export const useFindPartnerPopup = () => {
  271. setupRegionTagsFromBaseConsts()
  272. return {
  273. state: readonly(state),
  274. open,
  275. close,
  276. toggleTag,
  277. resetFilters,
  278. getFilters,
  279. onApply,
  280. applyFilters,
  281. }
  282. }