巴青农资商城

result.vue 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <view class="page-result">
  3. <view class="result-header" @click="onReSearch">
  4. <!-- <u-icon name="arrow-left" size="20" color="#333" @click.stop="onBack" /> -->
  5. <view class="result-header__keyword">
  6. <u-icon name="search" color="#999" size="16" />
  7. <text class="result-header__text">{{ keyword }}</text>
  8. </view>
  9. </view>
  10. <search-result-tabs
  11. :model-value="activeTab"
  12. :price-order="priceOrder"
  13. @update:model-value="activeTab = $event"
  14. @update:price-order="priceOrder = $event"
  15. @change="onTabChange"
  16. @price-change="onPriceOrderChange"
  17. />
  18. <scroll-view
  19. class="result-scroll"
  20. scroll-y
  21. :style="{ height: scrollHeight }"
  22. :scroll-top="listScrollTop"
  23. @scrolltolower="onLoadMore"
  24. >
  25. <view v-if="loading && !displayList.length" class="result-loading">
  26. <u-loading-icon mode="circle" />
  27. </view>
  28. <template v-else-if="isShopTab">
  29. <shop-list v-if="shopList.length" :list="shopList" @item-click="onShopClick" />
  30. <view v-else class="result-empty">
  31. <u-empty mode="search" :text="emptyText" icon-size="80" />
  32. <text v-if="loadFailed" class="result-retry" @click="reloadAll">点击重试</text>
  33. </view>
  34. </template>
  35. <template v-else>
  36. <goods-grid v-if="goodsList.length" :list="goodsList" @item-click="onGoodsClick" />
  37. <view v-else class="result-empty">
  38. <u-empty mode="search" :text="emptyText" icon-size="80" />
  39. <text v-if="loadFailed" class="result-retry" @click="reloadAll">点击重试</text>
  40. </view>
  41. </template>
  42. <view v-if="displayList.length" class="result-footer">
  43. <text v-if="loadingMore">加载中...</text>
  44. <text v-else-if="finished">没有更多了</text>
  45. </view>
  46. </scroll-view>
  47. </view>
  48. </template>
  49. <script setup>
  50. import { ref, computed } from 'vue'
  51. import { onLoad } from '@dcloudio/uni-app'
  52. import { searchAll } from '@/api/search'
  53. import { mapGoodsCardList } from '@/utils/goodsDisplay'
  54. import { mapShopCardList } from '@/utils/shopDisplay'
  55. import { addSearchHistory } from '@/utils/searchHistory'
  56. import { goSearchInput } from '@/utils/searchNav'
  57. import { goGoodsDetail } from '@/utils/goodsDetail'
  58. import {
  59. SEARCH_TAB_SALES,
  60. SEARCH_TAB_PRICE,
  61. SEARCH_TAB_SHOP,
  62. DEFAULT_GOODS_SORT,
  63. PRICE_ORDER_ASC,
  64. SEARCH_PAGE_SIZE
  65. } from '@/constants/search'
  66. import SearchResultTabs from '@/components/search/SearchResultTabs.vue'
  67. import GoodsGrid from '@/components/mall/GoodsGrid.vue'
  68. import ShopList from '@/components/search/ShopList.vue'
  69. const keyword = ref('')
  70. const activeTab = ref(SEARCH_TAB_SALES)
  71. const priceOrder = ref(PRICE_ORDER_ASC)
  72. const goodsList = ref([])
  73. const shopList = ref([])
  74. const goodsTotal = ref(0)
  75. const shopTotal = ref(0)
  76. const goodsPageNum = ref(1)
  77. const shopPageNum = ref(1)
  78. const loading = ref(false)
  79. const loadingMore = ref(false)
  80. const finished = ref(false)
  81. const loadFailed = ref(false)
  82. const listScrollTop = ref(0)
  83. const scrollHeight = ref('600px')
  84. const emptyText = '暂无相关'
  85. const isShopTab = computed(() => activeTab.value === SEARCH_TAB_SHOP)
  86. const displayList = computed(() => (isShopTab.value ? shopList.value : goodsList.value))
  87. const goodsSortBy = computed(() => {
  88. if (activeTab.value === SEARCH_TAB_SALES) {
  89. return DEFAULT_GOODS_SORT
  90. }
  91. if (activeTab.value === SEARCH_TAB_PRICE) {
  92. return priceOrder.value
  93. }
  94. return DEFAULT_GOODS_SORT
  95. })
  96. function calcScrollHeight() {
  97. try {
  98. const sys = uni.getSystemInfoSync()
  99. const h = sys.windowHeight || 600
  100. scrollHeight.value = `${h - 120}px`
  101. } catch (e) {
  102. scrollHeight.value = '500px'
  103. }
  104. }
  105. function resetScrollTop() {
  106. listScrollTop.value = listScrollTop.value === 0 ? 0.1 : 0
  107. }
  108. async function fetchSearch(reset) {
  109. const kw = (keyword.value || '').trim()
  110. if (!kw) return
  111. if (reset) {
  112. loading.value = true
  113. loadFailed.value = false
  114. finished.value = false
  115. if (isShopTab.value) {
  116. shopPageNum.value = 1
  117. } else {
  118. goodsPageNum.value = 1
  119. }
  120. } else {
  121. if (finished.value || loadingMore.value) return
  122. loadingMore.value = true
  123. }
  124. try {
  125. const params = {
  126. keyword: kw,
  127. sortBy: goodsSortBy.value,
  128. goodsPageNum: goodsPageNum.value,
  129. goodsPageSize: SEARCH_PAGE_SIZE,
  130. shopPageNum: shopPageNum.value,
  131. shopPageSize: SEARCH_PAGE_SIZE
  132. }
  133. const data = await searchAll(params)
  134. const goodsRows = mapGoodsCardList((data.goods && data.goods.rows) || [])
  135. const shopRows = mapShopCardList((data.shops && data.shops.rows) || [])
  136. const gTotal = Number((data.goods && data.goods.total) || 0)
  137. const sTotal = Number((data.shops && data.shops.total) || 0)
  138. if (isShopTab.value) {
  139. if (reset) {
  140. shopList.value = shopRows
  141. } else {
  142. shopList.value = shopList.value.concat(shopRows)
  143. }
  144. shopTotal.value = sTotal
  145. finished.value =
  146. shopRows.length < SEARCH_PAGE_SIZE || (sTotal > 0 && shopList.value.length >= sTotal)
  147. if (!finished.value) {
  148. shopPageNum.value += 1
  149. }
  150. } else {
  151. if (reset) {
  152. goodsList.value = goodsRows
  153. } else {
  154. goodsList.value = goodsList.value.concat(goodsRows)
  155. }
  156. goodsTotal.value = gTotal
  157. finished.value =
  158. goodsRows.length < SEARCH_PAGE_SIZE || (gTotal > 0 && goodsList.value.length >= gTotal)
  159. if (!finished.value) {
  160. goodsPageNum.value += 1
  161. }
  162. // 同一次请求顺带更新店铺缓存,切「店铺」Tab 可直接展示
  163. if (reset && shopPageNum.value === 1) {
  164. shopList.value = shopRows
  165. shopTotal.value = sTotal
  166. if (shopRows.length > 0) {
  167. shopPageNum.value = 2
  168. }
  169. }
  170. }
  171. } catch (e) {
  172. loadFailed.value = true
  173. if (reset) {
  174. if (isShopTab.value) {
  175. shopList.value = []
  176. } else {
  177. goodsList.value = []
  178. }
  179. }
  180. } finally {
  181. loading.value = false
  182. loadingMore.value = false
  183. }
  184. }
  185. function reloadAll() {
  186. resetScrollTop()
  187. if (isShopTab.value) {
  188. shopPageNum.value = 1
  189. } else {
  190. goodsPageNum.value = 1
  191. }
  192. fetchSearch(true)
  193. }
  194. function onTabChange() {
  195. resetScrollTop()
  196. finished.value = false
  197. if (isShopTab.value) {
  198. if (shopList.value.length > 0) {
  199. finished.value = shopTotal.value > 0 && shopList.value.length >= shopTotal.value
  200. return
  201. }
  202. shopPageNum.value = 1
  203. fetchSearch(true)
  204. } else {
  205. goodsPageNum.value = 1
  206. fetchSearch(true)
  207. }
  208. }
  209. function onPriceOrderChange() {
  210. if (activeTab.value !== SEARCH_TAB_PRICE) return
  211. resetScrollTop()
  212. goodsPageNum.value = 1
  213. fetchSearch(true)
  214. }
  215. function onLoadMore() {
  216. if (!loading.value && !loadingMore.value && !finished.value) {
  217. fetchSearch(false)
  218. }
  219. }
  220. function onBack() {
  221. uni.navigateBack()
  222. }
  223. function onReSearch() {
  224. goSearchInput(keyword.value)
  225. }
  226. function onGoodsClick(item) {
  227. if (item && item.goodsId) {
  228. goGoodsDetail(item.goodsId)
  229. }
  230. }
  231. function onShopClick() {
  232. uni.showToast({ title: '店铺主页开发中', icon: 'none' })
  233. }
  234. onLoad((options) => {
  235. calcScrollHeight()
  236. const kw = options && options.keyword ? decodeURIComponent(options.keyword) : ''
  237. keyword.value = (kw || '').trim()
  238. if (!keyword.value) {
  239. uni.showToast({ title: '请输入搜索内容', icon: 'none' })
  240. setTimeout(() => uni.navigateBack(), 500)
  241. return
  242. }
  243. addSearchHistory(keyword.value)
  244. fetchSearch(true)
  245. })
  246. </script>
  247. <style lang="scss" scoped>
  248. .page-result {
  249. display: flex;
  250. flex-direction: column;
  251. height: calc(100vh - 100rpx);
  252. background: #f5f6f8;
  253. }
  254. .result-header {
  255. display: flex;
  256. align-items: center;
  257. padding: 16rpx 24rpx;
  258. background: #e8f5e9;
  259. border-bottom: 1rpx solid #c8e6c9;
  260. }
  261. .result-header__keyword {
  262. flex: 1;
  263. display: flex;
  264. align-items: center;
  265. height: 64rpx;
  266. margin-left: 16rpx;
  267. padding: 0 20rpx;
  268. background: #fff;
  269. border: 1rpx solid #a5d6a7;
  270. border-radius: 32rpx;
  271. }
  272. .result-header__text {
  273. margin-left: 12rpx;
  274. font-size: 28rpx;
  275. color: #333;
  276. overflow: hidden;
  277. text-overflow: ellipsis;
  278. white-space: nowrap;
  279. }
  280. .result-scroll {
  281. flex: 1;
  282. box-sizing: border-box;
  283. padding: 10rpx;
  284. }
  285. .result-loading {
  286. padding: 80rpx 0;
  287. display: flex;
  288. justify-content: center;
  289. }
  290. .result-empty {
  291. padding: 60rpx 0;
  292. display: flex;
  293. flex-direction: column;
  294. align-items: center;
  295. }
  296. .result-retry {
  297. margin-top: 24rpx;
  298. font-size: 28rpx;
  299. color: #2e7d32;
  300. }
  301. .result-footer {
  302. padding: 24rpx;
  303. text-align: center;
  304. font-size: 24rpx;
  305. color: #999;
  306. }
  307. </style>