Search.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <template>
  2. <div class="pages--search">
  3. <div class="j-header jy-app-header" :class="{'h5-header': $envs.inH5}">
  4. <div class="search-header-flex">
  5. <van-icon class="header-left" name="arrow-left" @click="clickLeft" />
  6. <search id="mySearch" ref="input" key="search-page" :defalultValue="listState.value" @input="onInput"
  7. @submit="doSearch"></search>
  8. <span class="header-right" @click="doSearch">搜索</span>
  9. </div>
  10. <!-- <van-tabs v-model="docsTypeConf.active"
  11. v-if="docsTypeConf.list.length"
  12. title-active-color="#2ABED1"
  13. title-inactive-color="#5F5E64"
  14. color="#2ABED1"
  15. @change="docTypeChange"
  16. >
  17. <van-tab v-for="(item, index) in docsTypeConf.list" :key="index" :title="item.label" :name="item.type"></van-tab>
  18. </van-tabs> -->
  19. <div class="filter-box" v-if="listState.loaded">
  20. <Filters @onReset="filtersReset" @confirm="filtersConfirm" :saveActive="true"
  21. :saveActiveData="{ chooseFormat: listState.docFileType, chooseType: listState.productType }"></Filters>
  22. <div class="sort-list flex-r-c">
  23. <div class="sort-list-item flex-r-c left flex" :class="{
  24. active: item.active,
  25. reverse: item.sort
  26. }" v-for="(item, index) in sortTypeList" :key="index" @click="sortAndSearch(item, index)">
  27. <span class="s-i-label">{{ item.label }}</span>
  28. <van-icon :name="'diy-down-' + (item.active ? 'blue' : 'grey')" class="s-i-icon" />
  29. </div>
  30. </div>
  31. </div>
  32. </div>
  33. <div class="j-main" ref="scrollWrap">
  34. <div class="historySearch" v-if="!listState.loaded && historykeyList.length > 0 && isShowHistory">
  35. <div class="his_head">
  36. <div class="his_title">历史搜索</div>
  37. <van-icon :name="'diy-clear'" @click="clearHistory" />
  38. </div>
  39. <div class="his_content">
  40. <div class="his_item" v-for="item in historykeyList" :key="item.label" @click="historykeySearch(item.label)">{{ item.label }}
  41. </div>
  42. </div>
  43. </div>
  44. <van-list v-model="listState.loading" :finished="listState.finished" :offset="listState.offset" @load="getList"
  45. class="more-list calc-height-1px" ref="vanList">
  46. <div>
  47. <Card v-for="(item, index) in listState.list" :key="index" :title="item.docName" :desc="item.docSummary"
  48. :docType="item.docFileType" :isVip="item.productType === 1" :subInfo="calcSubInfo(item)"
  49. @onClick="toDocDetail(item)" />
  50. </div>
  51. <Empty v-if="listState.list.length === 0 && listState.loaded && !listState.loading" style="background: #fff;">
  52. 暂无数据</Empty>
  53. </van-list>
  54. </div>
  55. </div>
  56. </template>
  57. <script lang="ts">
  58. import { Component, Vue } from 'vue-property-decorator'
  59. import { Tabs, Tab, Icon, List } from 'vant'
  60. import Search from '@/components/Search.vue'
  61. import Filters from '@/components/filters/index.vue'
  62. import Card from '@/components/docs-card/Card.vue'
  63. import Empty from '@/components/common/Empty.vue'
  64. import { mapState, mapMutations, mapActions } from 'vuex'
  65. import { dateFormatter } from '@/utils/globalFunctions'
  66. @Component({
  67. name: 'search-page',
  68. components: {
  69. [Tab.name]: Tab,
  70. [Tabs.name]: Tabs,
  71. [List.name]: List,
  72. [Icon.name]: Icon,
  73. Search,
  74. Card,
  75. Empty,
  76. Filters
  77. },
  78. computed: {
  79. ...mapState('main', {
  80. searchState: (state: any) => state.searchPageData
  81. })
  82. },
  83. methods: {
  84. ...mapState([
  85. 'layoutConf',
  86. 'defaultLayoutConf'
  87. ]),
  88. ...mapMutations({
  89. saveSearchState: 'main/saveSearchPageState',
  90. clearSearchState: 'main/clearSearchPageState',
  91. updateLayoutConfig: 'updateLayoutConfig'
  92. }),
  93. ...mapActions({
  94. doSearchRquesst: 'main/doSearchDocs',
  95. getIndexTags: 'main/getIndexTags'
  96. })
  97. }
  98. })
  99. export default class extends Vue {
  100. protected searchState: any
  101. protected saveSearchState: any
  102. protected clearSearchState: any
  103. protected layoutConf!: any
  104. protected doSearchRquesst: any
  105. protected getIndexTags: any
  106. protected updateLayoutConfig!: any
  107. $envs: any
  108. historykeyList: string[] = []
  109. docsTypeConf = {
  110. active: '',
  111. list: []
  112. }
  113. sortTypeList = [
  114. {
  115. type: 'tSort',
  116. label: '上传时间',
  117. sort: 0,
  118. active: true
  119. },
  120. {
  121. type: 'dSort',
  122. label: '下载次数',
  123. sort: 0,
  124. active: false
  125. },
  126. {
  127. type: 'vSort',
  128. label: '浏览人数',
  129. sort: 0,
  130. active: false
  131. }
  132. ]
  133. listState = {
  134. docFileType: 0,
  135. productType: 0,
  136. value: '',
  137. loaded: false, // 是否首次加载完成
  138. loading: false,
  139. finished: true,
  140. pageNum: 1,
  141. pageSize: 10,
  142. offset: 80,
  143. scrollTop: 0,
  144. total: 0,
  145. list: []
  146. }
  147. restored = false // 当前数据是否走过缓存
  148. isShowHistory = false // 是否展示搜索历史(P572需求-已将历史记录移至剑鱼搜索页-文档搜索tab),本地开发调试可将置为true
  149. get activeSortType () {
  150. return this.sortTypeList.find(item => {
  151. return item.active
  152. })
  153. }
  154. created () {
  155. this.restored = this.reStoreState()
  156. if (!this.restored) {
  157. // this.getTags()
  158. }
  159. }
  160. mounted () {
  161. if (!this.restored) {
  162. this.onFocus()
  163. }
  164. if (this.$route.query.text && !this.listState.value) {
  165. this.listState.value = this.$route.query.text as any
  166. this.doSearch()
  167. }
  168. // if (localStorage.getItem('jydocs-searchHistory')) {
  169. // this.historykeyList = JSON.parse(localStorage.getItem('jydocs-searchHistory') || '[]')
  170. // }
  171. const storage = localStorage.getItem('JY-MOBILE--login-clear-AllSearchHistory')
  172. if (storage) {
  173. const docStorage = JSON.parse(storage)
  174. this.historykeyList = docStorage?.data.docs
  175. }
  176. this.updateLayoutConfig({
  177. actionLeftHide: true,
  178. headerStyle: {
  179. display: 'none',
  180. height: 0
  181. }
  182. })
  183. }
  184. onFocus () {
  185. const dom = document.querySelector('#mySearch input') as HTMLInputElement
  186. if (dom) {
  187. this.$nextTick(() => {
  188. setTimeout(() => {
  189. dom.focus()
  190. }, 200)
  191. })
  192. }
  193. }
  194. // 恢复数据至第一次请求的状态(页码等)
  195. resetListState () {
  196. const state = {
  197. loading: false,
  198. finished: true,
  199. pageNum: 1,
  200. total: 0,
  201. scrollTop: 0,
  202. list: []
  203. }
  204. Object.assign(this.listState, state)
  205. }
  206. onInput (search: string) {
  207. this.listState.value = search.trim().replace(/\s+/g, ' ')
  208. if (!this.listState.value) {
  209. history.back()
  210. }
  211. }
  212. historykeySearch (val: any) {
  213. this.listState.value = val
  214. this.doSearch()
  215. }
  216. savesearchHistory () {
  217. const key: string = this.listState.value + ''
  218. // 兼容剑鱼搜索通用缓存格式 JY-MOBILE--login-clear-AllSearchHistory
  219. const storage = localStorage.getItem('JY-MOBILE--login-clear-AllSearchHistory')
  220. if (storage) {
  221. const docStorage = JSON.parse(storage)
  222. this.historykeyList = docStorage?.data.docs
  223. }
  224. const isHave = this.historykeyList.some((v: any) => {
  225. return v.label === key
  226. })
  227. if (key && !isHave) {
  228. const items: any = { label: key }
  229. this.historykeyList.unshift(items)
  230. if (this.historykeyList.length > 10) {
  231. this.historykeyList.pop()
  232. }
  233. // localStorage.setItem('jydocs-searchHistory', JSON.stringify(this.historykeyList))
  234. this.setJySearchHistory()
  235. } else if (isHave) {
  236. console.log('搜索的标签在历史搜索列表内或点击已有的标签需要将标签顺序移至前面')
  237. const items: any = { label: key }
  238. // 删除当前已有的标签(过滤)
  239. const filterList = this.historykeyList.filter((item: any) => {
  240. return item.label !== key
  241. })
  242. this.historykeyList = filterList
  243. this.historykeyList.unshift(items)
  244. if (this.historykeyList.length > 10) {
  245. this.historykeyList.pop()
  246. }
  247. this.setJySearchHistory()
  248. }
  249. }
  250. setJySearchHistory () {
  251. const storage = localStorage.getItem('JY-MOBILE--login-clear-AllSearchHistory')
  252. if (storage) {
  253. const docStorage = JSON.parse(storage)
  254. docStorage.data.docs = this.historykeyList
  255. localStorage.setItem('JY-MOBILE--login-clear-AllSearchHistory', JSON.stringify(docStorage))
  256. } else {
  257. const searchHistory = {
  258. data: {
  259. bidding: [],
  260. buyer: [],
  261. company: [],
  262. supplier: [],
  263. docs: this.historykeyList
  264. }
  265. }
  266. localStorage.setItem('JY-MOBILE--login-clear-AllSearchHistory', JSON.stringify(searchHistory))
  267. }
  268. }
  269. clearHistory () {
  270. if (this.historykeyList.length === 0) return
  271. this.historykeyList = []
  272. // localStorage.removeItem('jydocs-searchHistory')
  273. const storage = localStorage.getItem('JY-MOBILE--login-clear-AllSearchHistory')
  274. if (storage) {
  275. const docStorage = JSON.parse(storage)
  276. docStorage.data.docs = this.historykeyList
  277. localStorage.setItem('JY-MOBILE--login-clear-AllSearchHistory', JSON.stringify(docStorage))
  278. }
  279. }
  280. docTypeChange () {
  281. if (!this.listState.value) return
  282. this.resetListState()
  283. this.setScrollTop()
  284. this.listState.finished = false
  285. this.getList()
  286. }
  287. sortAndSearch (item: any) {
  288. if (item.active) {
  289. // 改变sort
  290. // item.sort = item.sort ? 0 : 1
  291. } else {
  292. this.sortTypeList.forEach(s => {
  293. s.active = false
  294. })
  295. item.active = true
  296. }
  297. if (!this.listState.value) return
  298. this.resetListState()
  299. this.setScrollTop()
  300. this.listState.finished = false
  301. this.getList()
  302. }
  303. filtersReset (val: any) {
  304. this.listState.productType = val.type
  305. this.listState.docFileType = val.format
  306. this.doSearch()
  307. }
  308. filtersConfirm (val: any) {
  309. this.listState.productType = val.type
  310. this.listState.docFileType = val.format
  311. this.doSearch()
  312. }
  313. doSearch () {
  314. if (!this.listState.value) return
  315. this.$router.replace({
  316. query: { text: this.listState.value }
  317. })
  318. this.savesearchHistory()
  319. const inputComponent = this.$refs.input as any
  320. inputComponent.setSearchContent(this.listState.value)
  321. this.resetListState()
  322. this.setScrollTop()
  323. this.listState.finished = false
  324. this.getList()
  325. }
  326. toDocDetail (item: any) {
  327. const { docId: id } = item
  328. this.saveState()
  329. this.$router.push({
  330. name: 'details',
  331. params: { id }
  332. })
  333. }
  334. async getTags () {
  335. const { data } = await this.getIndexTags()
  336. if (Array.isArray(data)) {
  337. const list: any = data.map(item => {
  338. return {
  339. type: item,
  340. label: item
  341. }
  342. })
  343. this.docsTypeConf.list = list
  344. if (data.length) {
  345. const i: any = this.docsTypeConf.list[0]
  346. this.docsTypeConf.active = i.type
  347. }
  348. }
  349. return data
  350. }
  351. async getList () {
  352. if (!this.listState.value) return
  353. if (this.listState.pageNum === 1) {
  354. // this.$toast.loading({
  355. // forbidClick: true,
  356. // duration: 0
  357. // })
  358. }
  359. const query = {
  360. docFileType: this.listState.docFileType,
  361. productType: this.listState.productType,
  362. // 搜索关键字
  363. keyWord: this.listState.value,
  364. // tag: this.docsTypeConf.active === '全部' ? '' : this.docsTypeConf.active,
  365. sort: this.activeSortType?.type,
  366. num: this.listState.pageNum,
  367. size: this.listState.pageSize
  368. }
  369. console.log('搜索参数:', query)
  370. this.listState.loading = true
  371. const { data } = await this.doSearchRquesst(query)
  372. this.listState.loading = false
  373. this.listState.loaded = true
  374. // this.$toast.clear()
  375. if (data && Array.isArray(data.list)) {
  376. this.listState.pageNum += 1
  377. this.listState.total = data.total
  378. this.listState.list = this.listState.list.concat(data.list)
  379. } else {
  380. this.listState.finished = true
  381. }
  382. // 数据请求完成(根据页码计算,当前页是否是最后一页)
  383. // 请求完成后,页码就变为了下一页的页面,所以这里要-1
  384. const isLastPage = (this.listState.pageNum - 1) * this.listState.pageSize >= this.listState.total
  385. if (isLastPage) {
  386. this.listState.finished = true
  387. }
  388. }
  389. calcSubInfo (item: any) {
  390. const { uploadDate, downTimes } = item
  391. const subInfoArr = []
  392. if (uploadDate !== undefined) {
  393. subInfoArr.push(dateFormatter(uploadDate, 'yyyy/MM/dd'))
  394. }
  395. if (downTimes !== undefined) {
  396. subInfoArr.push(`${downTimes}次下载`)
  397. }
  398. return subInfoArr
  399. }
  400. setScrollTop () {
  401. this.$nextTick(() => {
  402. const wrapper: any = this.$refs.scrollWrap
  403. wrapper.scrollTop = this.listState.scrollTop
  404. })
  405. }
  406. reStoreState () {
  407. const listInfo = this.searchState
  408. if (!listInfo || Object.keys(listInfo).length === 0) {
  409. return false
  410. } else {
  411. for (const key in listInfo) {
  412. this.$data[key] = listInfo[key]
  413. }
  414. setTimeout(() => {
  415. this.setScrollTop()
  416. this.clearSearchState()
  417. }, 50)
  418. return true
  419. }
  420. }
  421. saveState () {
  422. const wrapper: any = this.$refs.scrollWrap
  423. this.listState.scrollTop = wrapper.scrollTop
  424. const d = {
  425. docsTypeConf: this.docsTypeConf,
  426. sortTypeList: this.sortTypeList,
  427. listState: this.listState
  428. }
  429. this.saveSearchState(d)
  430. }
  431. clickLeft () {
  432. history.back()
  433. }
  434. }
  435. </script>
  436. <style scoped lang="scss">
  437. @include diy-icon('down-grey', 16, 16);
  438. @include diy-icon('down-blue', 16, 16);
  439. @include diy-icon('clear', 20, 20);
  440. .pages--search {
  441. background-color: #fff;
  442. box-sizing: border-box;
  443. ::v-deep .van-tabs {
  444. width: 100%;
  445. font-size: 14px;
  446. line-height: 20px;
  447. }
  448. .j-header {
  449. height: auto!important;
  450. flex-direction: column;
  451. background: linear-gradient(280.62deg, #D7F6FB 1.93%, #E7FCFF 49.44%, #E7F2FF 98.41%)!important;
  452. }
  453. .j-header:after {
  454. height: 0;
  455. }
  456. .search-header-flex{
  457. width: 100%;
  458. display: flex;
  459. align-items: center;
  460. justify-content: space-between;
  461. padding-bottom: 12px;
  462. .header-right{
  463. flex-shrink: 0;
  464. font-size: 14px;
  465. line-height: 20px;
  466. color: #2ABED1;
  467. cursor: pointer;
  468. }
  469. ::v-deep{
  470. .my-search{
  471. padding: 0 12px;
  472. }
  473. .van-search__content{
  474. background: #fff;
  475. border-color: transparent;
  476. border-radius: 4px;
  477. }
  478. }
  479. }
  480. .filter-box {
  481. width: calc(100% + 24px);
  482. border-radius: 12px 12px 0 0;
  483. background: #fff;
  484. ::v-deep{
  485. .van-dropdown-menu__bar{
  486. background: unset;
  487. }
  488. }
  489. }
  490. .sort-list {
  491. width: 100%;
  492. font-size: 14px;
  493. line-height: 20px;
  494. border-bottom: 0.5px solid #0000000D;
  495. .sort-list-item {
  496. max-width: 108px;
  497. box-sizing: border-box;
  498. padding: 12px 16px;
  499. &.active {
  500. color: #2ABED1;
  501. }
  502. &.reverse {
  503. .s-i-icon {
  504. transform: rotate(180deg);
  505. }
  506. }
  507. .s-i-label {
  508. margin-right: 4px;
  509. }
  510. .s-i-icon {
  511. font-weight: bold;
  512. transition: transform .2 ease;
  513. }
  514. }
  515. }
  516. .historySearch {
  517. padding: 0 16px;
  518. .his_head {
  519. height: 54px;
  520. display: flex;
  521. justify-content: space-between;
  522. align-items: center;
  523. .his_title {
  524. font-size: 16px;
  525. color: #171826;
  526. }
  527. }
  528. .his_content {
  529. overflow: hidden;
  530. .his_item {
  531. float: left;
  532. margin-right: 8px;
  533. margin-bottom: 12px;
  534. padding: 4px 12px;
  535. line-height: 22px;
  536. font-size: 13px;
  537. color: #5F5E64;
  538. border-radius: 4px;
  539. background-color: #F7F9FA;
  540. }
  541. }
  542. }
  543. }
  544. </style>