header.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import {
  2. Sticky,
  3. Cell,
  4. Tag,
  5. Icon,
  6. Popup,
  7. Tabs,
  8. Tab,
  9. Dialog,
  10. Button,
  11. DropdownMenu,
  12. DropdownItem
  13. } from 'vant'
  14. import {
  15. RouterView,
  16. useRouter,
  17. useRoute,
  18. onBeforeRouteUpdate
  19. } from 'vue-router'
  20. import { defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'
  21. import mitt from 'eventemitter3'
  22. import Search from '@/components/col-search'
  23. import { useLocalStorage, useThrottleFn } from '@vueuse/core'
  24. import styles from './index.module.less'
  25. import classNames from 'classnames'
  26. import { getRandomKey } from '../music'
  27. import SelectSubject from './select-subject'
  28. import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
  29. import { state } from '@/state'
  30. import TheSticky from '@/components/the-sticky'
  31. import bgImg from '../../images/bg-image-search.png'
  32. import iconSearch from './icon-search.png'
  33. import request from '@/helpers/request'
  34. import { browser } from '@/helpers/utils'
  35. import ColHeader from '@/components/col-header'
  36. import ColResult from '@/components/col-result'
  37. import { postMessage } from '@/helpers/native-message'
  38. export const mitter = new mitt()
  39. const selectTagRef = ref()
  40. export const openWebViewOrWeb = (url: string, callback: any) => {
  41. if (browser().isApp) {
  42. postMessage({
  43. api: 'openWebView',
  44. content: {
  45. url: url, //`${location.origin}/tenant/#/music-album-detail/${item.id}`,
  46. orientation: 1,
  47. isHideTitle: false
  48. }
  49. })
  50. } else {
  51. // router.push({
  52. // path: '/music-album-detail/' + item.id
  53. // })
  54. callback && callback()
  55. }
  56. }
  57. export default defineComponent({
  58. name: 'MusicSearchHeader',
  59. setup() {
  60. const subjects: any = useSubjectId(SubjectEnum.SEARCH)
  61. // 判断是否已有数据
  62. if (!subjects.id) {
  63. const users = state.user.data
  64. const subjectId = users.subjectId
  65. ? Number(users.subjectId.split(',')[0])
  66. : ''
  67. const subjectName = users.subjectName
  68. ? users.subjectName.split(',')[0]
  69. : ''
  70. if (subjectId) {
  71. useSubjectId(
  72. SubjectEnum.SEARCH,
  73. JSON.stringify({
  74. id: subjectId,
  75. name: subjectName
  76. }),
  77. 'set'
  78. )
  79. }
  80. }
  81. localStorage.setItem('behaviorId', getRandomKey())
  82. const router = useRouter()
  83. const route = useRoute()
  84. const searchResultStatus = ref(false)
  85. const keyword = ref('')
  86. const tagids = ref('')
  87. const words = useLocalStorage<string[]>('music-search', [])
  88. const activeTab = ref('all')
  89. if (route.path === '/music-songbook/result') {
  90. keyword.value = route.query.search as string
  91. tagids.value = ''
  92. activeTab.value = 'all'
  93. }
  94. onBeforeRouteUpdate((to, form) => {
  95. const getSubject: any = useSubjectId(SubjectEnum.SEARCH)
  96. subject.name = getSubject.name || '全部声部'
  97. subject.id = getSubject.id
  98. if (route.path === '/music-songbook/search') {
  99. keyword.value = ''
  100. tagids.value = ''
  101. activeTab.value = 'all'
  102. try {
  103. selectTagRef.value?.resetTags?.()
  104. } catch (error) {
  105. console.log(error)
  106. }
  107. }
  108. if (to.path === '/music-songbook/result') {
  109. keyword.value = to.query.search as string
  110. console.log(keyword.value, 'value', route.query)
  111. tagids.value = ''
  112. activeTab.value = 'all'
  113. }
  114. return true
  115. })
  116. watch(activeTab, val => {
  117. mitter.emit('changeTab', val, keyword.value)
  118. })
  119. // 输入时搜索
  120. const searchList = ref<any>([])
  121. const onInputSearch = useThrottleFn(async (val: any) => {
  122. try {
  123. const { data } = await request.post('/api-student/music/sheet/search', {
  124. hideLoading: true,
  125. data: {
  126. subjectId: subjects.id,
  127. name: val,
  128. rows: 10
  129. }
  130. })
  131. const tempMusics = data.musicNames || []
  132. tempMusics.forEach((item: any, index: number) => {
  133. if (index < 10) {
  134. item.name = item.name.replace(val, `<span>${val}</span>`)
  135. }
  136. })
  137. searchList.value = tempMusics
  138. searchResultStatus.value = keyword.value ? true : false
  139. } catch {
  140. //
  141. }
  142. }, 300)
  143. const onSearch = val => {
  144. console.log('object :>> ', val)
  145. keyword.value = val
  146. const indexOf = words.value.indexOf(val)
  147. if (indexOf > -1) {
  148. words.value.splice(indexOf, 1)
  149. }
  150. if (val) {
  151. words.value.unshift(val)
  152. // console.log(words.value.length, 'words.value.length')
  153. words.value.length = Math.min(words.value.length, 10)
  154. if (route.path === '/music-songbook/search') defaultClose()
  155. }
  156. // mitter.emit('search', val)
  157. if (route.path !== '/music-songbook/result') {
  158. router.replace({
  159. path: '/music-songbook/result',
  160. query: {
  161. search: val
  162. }
  163. })
  164. searchResultStatus.value = false
  165. searchList.value = []
  166. } else {
  167. searchResultStatus.value = false
  168. mitter.emit('search', val)
  169. }
  170. }
  171. const searchRef = ref()
  172. const onComfirmSubject = (item: any) => {
  173. // console.log('onSort', item)
  174. subject.name = item.name
  175. subject.id = item.id
  176. searchRef.value.toggle()
  177. useSubjectId(
  178. SubjectEnum.SEARCH,
  179. JSON.stringify({
  180. id: item.id,
  181. name: item.name
  182. }),
  183. 'set'
  184. )
  185. subject.show = false
  186. mitter.emit('confirmSubject', subject)
  187. }
  188. const getSubject: any = useSubjectId(SubjectEnum.SEARCH)
  189. const subject = reactive({
  190. show: false,
  191. name: getSubject.name || '全部声部',
  192. id: getSubject.id || ''
  193. })
  194. const tagRef = ref<any>([])
  195. const collapse = reactive({
  196. line: 0,
  197. arrowStatus: false
  198. })
  199. // 历史搜索默认收起
  200. const defaultClose = () => {
  201. nextTick(() => {
  202. if (!words.value || !words.value.length) {
  203. return
  204. }
  205. let offsetLeft = -1
  206. collapse.line = 0
  207. const tags = tagRef.value
  208. tags.forEach((item: any, index: number) => {
  209. try {
  210. item.$el.style.display = 'block'
  211. if (index === 0) {
  212. collapse.line = 1
  213. offsetLeft = item.$el.offsetLeft
  214. } else if (item.$el.offsetLeft === offsetLeft && index != 0) {
  215. // 如果某个标签的offsetLeft和第一个标签的offsetLeft相等 说明增加了一行
  216. collapse.line += 1
  217. }
  218. if (!collapse.arrowStatus) {
  219. if (collapse.line > 2) {
  220. //从第3行开始 隐藏标签
  221. item.$el.style.display = 'none'
  222. } else {
  223. item.$el.style.display = 'block'
  224. }
  225. } else {
  226. item.$el.style.display = 'block'
  227. }
  228. } catch (e: any) {
  229. console.log(e, 'Error')
  230. }
  231. })
  232. })
  233. }
  234. // 首先调用默认收起的方法
  235. if (route.path === '/music-songbook/search') defaultClose()
  236. onMounted(() => {
  237. postMessage({
  238. api: 'backIconChange',
  239. content: { backIconHide: true }
  240. })
  241. })
  242. return () => {
  243. return (
  244. <div class={styles.search}>
  245. <div class={styles.sticky}>
  246. <TheSticky position="top">
  247. <ColHeader isFixed={false} background="transparent" title=" " />
  248. <Search
  249. modelValue={keyword.value}
  250. background="transparent"
  251. showAction
  252. onInput={(val: any) => {
  253. keyword.value = val
  254. if (val) {
  255. onInputSearch(val)
  256. } else {
  257. searchList.value = []
  258. searchResultStatus.value = false
  259. if (route.path === '/music-songbook/result') {
  260. router.replace('/music-songbook/search')
  261. }
  262. }
  263. }}
  264. onSearch={(val: any) => {
  265. if (!val) return
  266. keyword.value = val
  267. console.log(val, 'val')
  268. onSearch(val)
  269. // searchResultStatus.value = true
  270. }}
  271. type="tenant"
  272. v-slots={{
  273. left: () => (
  274. // <div
  275. // class={styles.label}
  276. // onClick={() => (subject.show = true)}
  277. // >
  278. // {subject.name}
  279. // <Icon
  280. // classPrefix="iconfont"
  281. // name="down"
  282. // size={12}
  283. // color="#333"
  284. // />
  285. // </div>
  286. <DropdownMenu>
  287. <DropdownItem
  288. titleClass={styles.titleActive}
  289. title="筛选"
  290. ref={searchRef}
  291. >
  292. <SelectSubject
  293. searchParams={subject}
  294. onComfirm={onComfirmSubject}
  295. />
  296. </DropdownItem>
  297. </DropdownMenu>
  298. ),
  299. action: () => (
  300. <span
  301. class={styles.searchCancel}
  302. onClick={() => {
  303. if (browser().isApp) {
  304. postMessage({ api: 'back' })
  305. } else {
  306. router.back()
  307. }
  308. }}
  309. >
  310. 取消
  311. </span>
  312. )
  313. }}
  314. />
  315. {route.path === '/music-songbook/result' &&
  316. !searchResultStatus.value && (
  317. <Tabs
  318. color="var(--van-primary)"
  319. background="transparent"
  320. lineWidth={20}
  321. shrink
  322. class={styles.tagTabs}
  323. v-model:active={activeTab.value}
  324. onChange={val => (activeTab.value = val)}
  325. >
  326. <Tab title="综合" name="all"></Tab>
  327. <Tab title="单曲" name="songe"></Tab>
  328. <Tab title="专辑" name="album"></Tab>
  329. </Tabs>
  330. )}
  331. </TheSticky>
  332. <img class={styles.bgImg} src={bgImg} />
  333. </div>
  334. {words.value.length > 0 && route.path === '/music-songbook/search' && (
  335. <div class={styles.keywordSection}>
  336. <div class={styles.keywordTitle}>
  337. <span class={styles.t}>搜索历史</span>
  338. <Icon
  339. class={styles.remove}
  340. name="delete-o"
  341. onClick={() => (words.value = [])}
  342. />
  343. </div>
  344. <div class={classNames(styles.keywords)}>
  345. <div class={styles.content}>
  346. {words.value.map((item: any, index: number) => (
  347. <Tag
  348. ref={(el: any) => (tagRef.value[index] = el)}
  349. round
  350. class={[styles.searchKeyword, 'van-ellipsis']}
  351. key={item}
  352. onClick={() => {
  353. keyword.value = item
  354. onSearch(item)
  355. }}
  356. >
  357. {item}
  358. </Tag>
  359. ))}
  360. {collapse.line > 2 && (
  361. <span
  362. class={[styles.arrowMore]}
  363. onClick={() => {
  364. collapse.arrowStatus = !collapse.arrowStatus
  365. defaultClose()
  366. }}
  367. >
  368. <Icon
  369. name={collapse.arrowStatus ? 'arrow-up' : 'arrow-down'}
  370. />
  371. </span>
  372. )}
  373. </div>
  374. </div>
  375. </div>
  376. )}
  377. {route.path === '/music-songbook/search' && (
  378. <Tabs
  379. color="var(--van-primary)"
  380. background="transparent"
  381. lineWidth={20}
  382. shrink
  383. class={styles.tagTabs}
  384. v-model:active={activeTab.value}
  385. onChange={val => (activeTab.value = val)}
  386. >
  387. <Tab title="综合" name="all"></Tab>
  388. <Tab title="单曲" name="songe"></Tab>
  389. <Tab title="专辑" name="album"></Tab>
  390. </Tabs>
  391. )}
  392. <RouterView />
  393. {/* 声部弹框 */}
  394. {/* <Popup
  395. show={subject.show}
  396. position="bottom"
  397. round
  398. closeable
  399. safe-area-inset-bottom
  400. onClose={() => (subject.show = false)}
  401. onClosed={() => (subject.show = false)}
  402. >
  403. <SelectSubject
  404. searchParams={subject}
  405. onComfirm={onComfirmSubject}
  406. />
  407. </Popup> */}
  408. <div
  409. class={[styles.searchResult]}
  410. style={{ display: searchResultStatus.value ? 'block' : 'none' }}
  411. >
  412. <div class={styles.searchGroups}>
  413. {searchList.value.map((item: any) => (
  414. <div
  415. class={styles.searchItem}
  416. onClick={() => {
  417. if (item.type === 'ALBUM') {
  418. openWebViewOrWeb(
  419. `${location.origin}/tenant/#/music-album-detail/${item.id}`,
  420. () => {
  421. router.push({
  422. path: '/music-album-detail/' + item.id
  423. })
  424. }
  425. )
  426. } else {
  427. openWebViewOrWeb(
  428. `${location.origin}/tenant/#/music-detail?id=${item.id}`,
  429. () => {
  430. router.push({
  431. path: '/music-detail',
  432. query: {
  433. id: item.id
  434. }
  435. })
  436. }
  437. )
  438. }
  439. }}
  440. >
  441. <img src={iconSearch} class={styles.iconSearch} />
  442. <span class={styles.rName} v-html={item.name}></span>
  443. </div>
  444. ))}
  445. {searchList.value.length <= 0 && (
  446. <ColResult tips="暂无搜索结果" btnStatus={false} />
  447. )}
  448. </div>
  449. </div>
  450. </div>
  451. )
  452. }
  453. }
  454. })