useFindPartnerPopup.ts 7.8 KB

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