search-result.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <template>
  2. <view class="search-result-page">
  3. <image class="top-bg-image" src="/pages-sell/static/top-bg.png" mode="widthFix"></image>
  4. <Navbar title="书嗨" :titleSize="36" :title-color="navbarTitleColor" :back-icon-color="navbarIconColor"
  5. :background="navbarBackground"></Navbar>
  6. <!-- Custom Header -->
  7. <view class="search-bar-wrapper">
  8. <u-search style="width: 100%;" v-model="keyword" placeholder="书名/作者/ISBN" :show-action="true"
  9. action-text="反馈" :action-style="{ color: '#fff' }" bg-color="#fff" shape="round" :clearabled="true"
  10. search-icon="/pages-sell/static/search-icon.png" @custom="toFeedback" @search="onSearch"></u-search>
  11. </view>
  12. <!-- Banner -->
  13. <view class="banner-section">
  14. <image src="/pages-sell/static/top-banner.png" class="top-banner" mode="widthFix"></image>
  15. </view>
  16. <!-- Content Area -->
  17. <view class="content-area">
  18. <!-- Everyone is watching -->
  19. <view class="section-block" v-if="hotBook && hotBook.isbn">
  20. <text class="section-title">大家都在看</text>
  21. <HotRecommendItem :item="hotBook" @add-cart="addToCart" @update-item="updateHotBook"></HotRecommendItem>
  22. </view>
  23. <!-- Filter Bar -->
  24. <view class="filter-bar">
  25. <view class="filter-item" :class="{ active: sort === 1 }" @click="handleSort(1)">
  26. <text>综合</text>
  27. <view class="sort-icons">
  28. <u-icon name="arrow-down-fill" :color="sort === 1 ? '#38C148' : '#cccccc'" size="14"></u-icon>
  29. </view>
  30. </view>
  31. <view class="filter-item" :class="{ active: sort === 4 || sort === 5 }" @click="handleSort('sales')">
  32. <text>销量</text>
  33. <view class="sort-icons">
  34. <u-icon name="arrow-up-fill" :color="sort === 4 ? '#38C148' : '#cccccc'" size="14"></u-icon>
  35. <u-icon name="arrow-down-fill" :color="sort === 5 ? '#38C148' : '#cccccc'" size="14"></u-icon>
  36. </view>
  37. </view>
  38. <view class="filter-item" :class="{ active: sort === 2 || sort === 3 }" @click="handleSort('price')">
  39. <text>价格</text>
  40. <view class="sort-icons">
  41. <u-icon name="arrow-up-fill" :color="sort === 2 ? '#38C148' : '#cccccc'" size="14"></u-icon>
  42. <u-icon name="arrow-down-fill" :color="sort === 3 ? '#38C148' : '#cccccc'" size="14"></u-icon>
  43. </view>
  44. </view>
  45. <view class="filter-item" @click="showFilter = true">
  46. <text>筛选</text>
  47. <image src="/pages-sell/static/search-result/icon-filter.png" class="icon-filter"></image>
  48. </view>
  49. </view>
  50. <!-- Book List -->
  51. <view class="book-list">
  52. <RecommendItem v-for="(item, index) in bookList" :key="index" :item="item" @add-cart="addToCart"
  53. @update-item="updateBookItem($event, index)" :show-desc="false">
  54. </RecommendItem>
  55. <u-loadmore :status="loadStatus" margin-top="30" margin-bottom="30"></u-loadmore>
  56. </view>
  57. </view>
  58. <!-- Filter Popup -->
  59. <u-popup v-model="showFilter" mode="bottom">
  60. <view class="filter-popup">
  61. <view class="filter-content">
  62. <view class="filter-title">价格区间</view>
  63. <view class="price-inputs">
  64. <u-input v-model="tempMinPrice" type="number" placeholder="最低价" border="surround"></u-input>
  65. <view class="divider">-</view>
  66. <u-input v-model="tempMaxPrice" type="number" placeholder="最高价" border="surround"></u-input>
  67. </view>
  68. </view>
  69. <view class="filter-footer">
  70. <view class="btn reset" @click="resetFilter">重置</view>
  71. <view class="btn confirm" @click="confirmFilter">确定</view>
  72. </view>
  73. </view>
  74. </u-popup>
  75. <!-- Red Packet Float -->
  76. <!-- <view class="red-packet-float" @click="openPacket">
  77. <view class="time-badge">
  78. <text>剩</text>
  79. <u-count-down :timestamp="timestamp" separator="colon" color="#DF1407" bg-color="transparent"
  80. font-size="18" separator-size="18" separator-color="#DF1407"></u-count-down>
  81. </view>
  82. <image src="/pages-sell/static/search/icon-red-packet.png" class="packet-img" mode="aspectFit"></image>
  83. </view> -->
  84. <!-- Surprise Packet Dialog -->
  85. <PacketDialog ref="packetDialog"></PacketDialog>
  86. <select-good-popup-host />
  87. </view>
  88. </template>
  89. <script>
  90. import RecommendItem from '../components/recommend-item/index.vue';
  91. import HotRecommendItem from '../components/hot-recommend-item/index.vue';
  92. import Navbar from '@/components/navbar/navbar.vue';
  93. import PacketDialog from '@/pages-sell/components/packet-dialog/index.vue';
  94. import SelectGoodPopupHost from '@/components/select-good-popup-host.vue';
  95. export default {
  96. components: {
  97. RecommendItem,
  98. HotRecommendItem,
  99. Navbar,
  100. PacketDialog,
  101. SelectGoodPopupHost,
  102. },
  103. data() {
  104. return {
  105. keyword: '',
  106. pageNum: 1,
  107. pageSize: 10,
  108. hotBook: {},
  109. bookList: [],
  110. loadStatus: 'loadmore',
  111. navbarBackground: 'transparent',
  112. navbarTitleColor: '#ffffff',
  113. navbarIconColor: '#ffffff',
  114. sort: 1, // 1-综合 2-价格升序 3-价格降序 4-销量升序 5-销量降序
  115. showFilter: false,
  116. tempMinPrice: '',
  117. tempMaxPrice: '',
  118. minPrice: '',
  119. maxPrice: '',
  120. timestamp: 86400, // 24 hours countdown
  121. }
  122. },
  123. onPullDownRefresh() {
  124. this.pageNum = 1;
  125. this.loadStatus = 'loading';
  126. this.loadData();
  127. },
  128. onReachBottom() {
  129. if (this.loadStatus === 'nomore') return;
  130. this.pageNum++;
  131. this.loadStatus = 'loading';
  132. this.loadData();
  133. },
  134. onPageScroll(e) {
  135. if (e.scrollTop > 50) {
  136. this.navbarBackground = '#ffffff';
  137. this.navbarTitleColor = '#000000';
  138. this.navbarIconColor = '#000000';
  139. } else {
  140. this.navbarBackground = 'transparent';
  141. this.navbarTitleColor = '#ffffff';
  142. this.navbarIconColor = '#ffffff';
  143. }
  144. },
  145. onLoad(options) {
  146. this.keyword = options.keyword ? decodeURIComponent(options.keyword) : '';
  147. this.loadData();
  148. },
  149. methods: {
  150. loadData() {
  151. const params = {
  152. keyword: this.keyword,
  153. pageNum: this.pageNum,
  154. pageSize: this.pageSize,
  155. sort: this.sort
  156. };
  157. if (this.minPrice) params.minPrice = this.minPrice;
  158. if (this.maxPrice) params.maxPrice = this.maxPrice;
  159. this.$u.api.getSearchKeywordAjax(params).then(res => {
  160. uni.stopPullDownRefresh();
  161. const rows = res.data.rows || [];
  162. if (this.pageNum === 1) {
  163. this.bookList = rows;
  164. } else {
  165. this.bookList = [...this.bookList, ...rows];
  166. }
  167. if (rows.length < this.pageSize) {
  168. this.loadStatus = 'nomore';
  169. } else {
  170. this.loadStatus = 'loadmore';
  171. }
  172. if (res.data.couponName) {
  173. this.openPacket({
  174. couponName: res.data.couponName,
  175. faceMoney: res.data.faceMoney,
  176. thresholdMoney: res.data.thresholdMoney,
  177. });
  178. }
  179. }).catch(() => {
  180. uni.stopPullDownRefresh();
  181. this.loadStatus = 'loadmore';
  182. });
  183. if (this.pageNum === 1) {
  184. this.$u.api.getSearchRecommendAjax(this.keyword).then(res => {
  185. if (res.code == 200) {
  186. res.data.discountDesc = res.data.productPrice ? ((res.data.productPrice / res.data.price) * 10).toFixed(2) + '折' : '';
  187. res.data.discountPrice = res.data.productPrice ? (res.data.price - res.data.productPrice).toFixed(2) : '';
  188. this.hotBook = res.data;
  189. }
  190. });
  191. }
  192. },
  193. handleSort(type) {
  194. if (type === 1) {
  195. if (this.sort === 1) return;
  196. this.sort = 1;
  197. } else if (type === 'price') {
  198. if (this.sort === 2) {
  199. this.sort = 3;
  200. } else {
  201. this.sort = 2;
  202. }
  203. } else if (type === 'sales') {
  204. if (this.sort === 5) {
  205. this.sort = 4;
  206. } else {
  207. this.sort = 5;
  208. }
  209. }
  210. this.pageNum = 1;
  211. this.loadStatus = 'loading';
  212. this.loadData();
  213. },
  214. resetFilter() {
  215. this.tempMinPrice = '';
  216. this.tempMaxPrice = '';
  217. },
  218. confirmFilter() {
  219. this.minPrice = this.tempMinPrice;
  220. this.maxPrice = this.tempMaxPrice;
  221. this.showFilter = false;
  222. this.pageNum = 1;
  223. this.loadStatus = 'loading';
  224. this.loadData();
  225. },
  226. goBack() {
  227. uni.navigateBack();
  228. },
  229. toFeedback() {
  230. uni.navigateTo({
  231. url: '/pages-sell/pages/feedback'
  232. });
  233. },
  234. onSearch(val) {
  235. console.log('Search:', val);
  236. this.keyword = val;
  237. this.pageNum = 1;
  238. this.loadData();
  239. },
  240. addToCart(data) {
  241. // data now contains { product, quality, quantity } from the popup
  242. console.log('Added to cart:', data);
  243. uni.showToast({
  244. title: '已加入购物车',
  245. icon: 'success',
  246. duration: 3000
  247. });
  248. },
  249. updateBookItem(updatedItem, index) {
  250. this.$set(this.bookList, index, { ...this.bookList[index], ...updatedItem });
  251. },
  252. updateHotBook(updatedItem) {
  253. this.hotBook = { ...this.hotBook, ...updatedItem };
  254. },
  255. openPacket(data) {
  256. this.$refs.packetDialog.open(data);
  257. },
  258. closePacket() {
  259. this.$refs.packetDialog.close();
  260. }
  261. },
  262. onShareAppMessage(res) {
  263. if (res.from === "button" && res.target && res.target.dataset && res.target.dataset.shareType === "reduce") {
  264. let reduceCode = uni.getStorageSync("reduceCodeShare");
  265. if (this.$u && this.$u.api && this.$u.api.goToReduceShareAjax) {
  266. this.$u.api.goToReduceShareAjax({ reduceCode: reduceCode });
  267. } else {
  268. uni.$u && uni.$u.http && uni.$u.http.get('/token/shop/order/goToShare', { reduceCode: reduceCode });
  269. }
  270. return {
  271. title: "快来帮我减钱买书吧!",
  272. path: "/pages/sell/index?reduceCode=" + reduceCode,
  273. imageUrl: "https://shuhi.oss-cn-qingdao.aliyuncs.com/mini/share.jpg",
  274. desc: "书嗨,专注于书籍交易的平台,提供新书和二手书的买卖服务",
  275. };
  276. }
  277. return {
  278. title: "书嗨",
  279. path: "/pages-sell/pages/search-result?keyword=" + (this.keyword || ""),
  280. };
  281. }
  282. }
  283. </script>
  284. <style lang="scss" scoped>
  285. .search-result-page {
  286. min-height: 100vh;
  287. background-color: #F6F6F6;
  288. font-family: 'Source Han Sans SC', sans-serif;
  289. position: relative;
  290. }
  291. /* 顶部大背景图 */
  292. .top-bg-image {
  293. position: fixed;
  294. top: 0;
  295. left: 0;
  296. width: 100%;
  297. z-index: 0;
  298. display: block;
  299. }
  300. .search-bar-wrapper {
  301. position: relative;
  302. z-index: 2;
  303. display: flex;
  304. align-items: center;
  305. padding: 0 30rpx;
  306. height: 88rpx;
  307. width: 100%;
  308. }
  309. .banner-section {
  310. position: relative;
  311. width: 100%;
  312. z-index: 1;
  313. padding: 10rpx 30rpx;
  314. .top-banner {
  315. width: 100%;
  316. display: block;
  317. }
  318. }
  319. .content-area {
  320. position: relative;
  321. z-index: 2;
  322. padding: 30rpx 30rpx;
  323. border-radius: 30rpx 30rpx 0 0;
  324. min-height: 500rpx;
  325. .section-block {
  326. margin-bottom: 30rpx;
  327. background-color: #fff;
  328. padding: 30rpx 24rpx;
  329. border-radius: 20rpx;
  330. .section-title {
  331. font-size: 32rpx;
  332. font-weight: bold;
  333. color: #333;
  334. margin-bottom: 20rpx;
  335. display: block;
  336. }
  337. }
  338. }
  339. .filter-bar {
  340. display: flex;
  341. justify-content: space-between;
  342. align-items: center;
  343. margin-bottom: 20rpx;
  344. .filter-item {
  345. display: flex;
  346. align-items: center;
  347. font-size: 28rpx;
  348. color: #666;
  349. &.active {
  350. color: #333;
  351. font-weight: bold;
  352. .triangle-icon {
  353. border-top-color: #333;
  354. }
  355. }
  356. text {
  357. margin-right: 6rpx;
  358. }
  359. .triangle-icon {
  360. width: 0;
  361. height: 0;
  362. border-left: 8rpx solid transparent;
  363. border-right: 8rpx solid transparent;
  364. border-top: 10rpx solid #666;
  365. }
  366. .sort-icons {
  367. display: flex;
  368. flex-direction: column;
  369. margin-left: 4rpx;
  370. justify-content: center;
  371. gap: 4rpx;
  372. u-icon {
  373. margin: -2rpx 0;
  374. }
  375. }
  376. .icon-filter {
  377. width: 24rpx;
  378. height: 24rpx;
  379. margin-left: 4rpx;
  380. }
  381. }
  382. }
  383. .book-list {
  384. background: #fff;
  385. border-radius: 20rpx;
  386. padding: 0 24rpx;
  387. padding-bottom: 40rpx;
  388. }
  389. .filter-popup {
  390. background-color: #fff;
  391. height: 100%;
  392. display: flex;
  393. flex-direction: column;
  394. .filter-content {
  395. flex: 1;
  396. padding: 30rpx;
  397. .filter-title {
  398. font-size: 30rpx;
  399. font-weight: bold;
  400. color: #333;
  401. margin-bottom: 30rpx;
  402. }
  403. .price-inputs {
  404. display: flex;
  405. align-items: center;
  406. justify-content: space-between;
  407. u-input {
  408. flex: 1;
  409. }
  410. .divider {
  411. margin: 0 20rpx;
  412. color: #999;
  413. }
  414. }
  415. }
  416. .filter-footer {
  417. display: flex;
  418. height: 100rpx;
  419. .btn {
  420. flex: 1;
  421. display: flex;
  422. align-items: center;
  423. justify-content: center;
  424. font-size: 32rpx;
  425. &.reset {
  426. background-color: #fff;
  427. color: #666;
  428. border-top: 1rpx solid #eee;
  429. }
  430. &.confirm {
  431. background-color: #38C148;
  432. color: #fff;
  433. }
  434. }
  435. }
  436. }
  437. .red-packet-float {
  438. position: fixed;
  439. bottom: 200rpx;
  440. right: 40rpx;
  441. z-index: 100;
  442. display: flex;
  443. flex-direction: column;
  444. align-items: center;
  445. .time-badge {
  446. background: #FFE173;
  447. color: #DF1407;
  448. font-size: 18rpx;
  449. padding: 4rpx 10rpx;
  450. border-radius: 10rpx;
  451. position: absolute;
  452. top: -40rpx;
  453. right: -20rpx;
  454. white-space: nowrap;
  455. z-index: 2;
  456. display: flex;
  457. align-items: center;
  458. text {
  459. margin-right: 4rpx;
  460. }
  461. }
  462. .packet-img {
  463. width: 100rpx;
  464. height: 120rpx;
  465. }
  466. }
  467. </style>