木邻有你前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

422 lines
10 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  1. <template>
  2. <view class="shop-content">
  3. <!-- 搜索框 -->
  4. <view class="search-container">
  5. <uv-search
  6. v-model="title"
  7. placeholder="搜索商品名"
  8. :show-action="false"
  9. bg-color="#f3f7f8"
  10. inputAlign="left"
  11. height="40"
  12. margin="10rpx"
  13. @search="onSearch"
  14. @clickIcon="onSearch"
  15. @clear="onSearch"
  16. ></uv-search>
  17. </view>
  18. <!-- <Search
  19. v-model="title"
  20. placeholder="🔍搜索商品名"
  21. :show-cancel="false"
  22. :show-icon="false"
  23. text-align="center"
  24. height="80rpx"
  25. width="90%"
  26. bg-color="#f3f7f8"
  27. @search="onSearch"
  28. @clickIcon="onSearch"
  29. @clear="onSearch"
  30. style="margin: 4rpx 0rpx;"
  31. /> -->
  32. <!-- Tab栏 -->
  33. <view class="tab-container">
  34. <scroll-view scroll-x="true" class="tab-scroll">
  35. <view class="tab-list">
  36. <!-- 固定的前三个Tab -->
  37. <view
  38. class="tab-item"
  39. :class="{ active: currentTab === 0 }"
  40. @click="onTabClick(0, '全部')"
  41. >
  42. <text class="tab-text">全部</text>
  43. </view>
  44. <view
  45. class="tab-item sort-tab"
  46. :class="{ active: currentTab === 1 }"
  47. @click="onTabClick(1, '兑换积分')"
  48. >
  49. <text class="tab-text">兑换积分</text>
  50. <view class="sort-arrows">
  51. <view class="arrow up" :class="{ active: sortType === 'points_asc' }"></view>
  52. <view class="arrow down" :class="{ active: sortType === 'points_desc' }"></view>
  53. </view>
  54. </view>
  55. <view
  56. class="tab-item sort-tab"
  57. :class="{ active: currentTab === 2 }"
  58. @click="onTabClick(2, '兑换量')"
  59. >
  60. <text class="tab-text">兑换量</text>
  61. <view class="sort-arrows">
  62. <view class="arrow up" :class="{ active: sortType === 'exchange_asc' }"></view>
  63. <view class="arrow down" :class="{ active: sortType === 'exchange_desc' }"></view>
  64. </view>
  65. </view>
  66. <!-- 从store获取的商品分类Tab -->
  67. <view
  68. v-for="(category, index) in categoryGoodsList"
  69. :key="category.id"
  70. class="tab-item"
  71. :class="{ active: currentTab === index + 3 }"
  72. @click="onTabClick(index + 3, category.title, category.id)"
  73. >
  74. <text class="tab-text">{{ category.title }}</text>
  75. </view>
  76. </view>
  77. </scroll-view>
  78. </view>
  79. <!-- 商品列表 -->
  80. <view class="goods-container">
  81. <view class="goods-grid" v-if="goodsList.length > 0">
  82. <view
  83. class="goods-item click-animation"
  84. v-for="(item, index) in goodsList"
  85. :key="index"
  86. @click="onGoodsClick(item)"
  87. >
  88. <view class="goods-image">
  89. <image :src="item.image" mode="aspectFit" class="image"></image>
  90. </view>
  91. <view class="goods-info">
  92. <text class="goods-name">{{ item.title }}</text>
  93. <view class="goods-bottom">
  94. <view class="points-info">
  95. <image src="/static/积分图标.png" class="points-icon" mode="aspectFit"></image>
  96. <text class="points-text">{{ item.price }}积分</text>
  97. </view>
  98. <uv-button
  99. type="primary"
  100. size="mini"
  101. text="立即兑换"
  102. :custom-style="buttonStyle"
  103. ></uv-button>
  104. </view>
  105. </view>
  106. </view>
  107. </view>
  108. <uv-empty
  109. v-else
  110. icon="/static/暂无搜索结果.png"
  111. text="暂无商品数据"
  112. ></uv-empty>
  113. </view>
  114. </view>
  115. </template>
  116. <script>
  117. import Search from '@/pages/components/Search.vue'
  118. export default {
  119. name: 'ShopContent',
  120. data() {
  121. return {
  122. // searchValue: '',
  123. currentTab: 0,
  124. pageNo: 1,
  125. pageSize: 10,
  126. title: '',
  127. hasMore: true,
  128. sortType: '', // 排序类型:points_asc, points_desc, exchange_asc, exchange_desc
  129. goodsList: [],
  130. buttonStyle: {
  131. width: '128rpx',
  132. height: '44rpx',
  133. borderRadius: '28rpx',
  134. fontSize: '22rpx'
  135. },
  136. // 额外的传参
  137. extraParams : {}
  138. }
  139. },
  140. components: {
  141. Search
  142. },
  143. computed: {
  144. // 从store获取商品分类列表
  145. categoryGoodsList() {
  146. return this.$store.state.categoryGoodsList || []
  147. }
  148. },
  149. methods: {
  150. async onSearch(value) {
  151. if (value !== null || undefined) this.title = value
  152. this.initData()
  153. await this.getGoodsList({isRefresh : true})
  154. },
  155. async onTabClick(index, tabName, categoryId = null) {
  156. this.currentTab = index
  157. this.extraParams = {} // 不带任何额外参数
  158. if (index === 0) {
  159. // 全部Tab
  160. console.log('点击了全部Tab')
  161. } else if (index === 1) {
  162. // 兑换积分Tab - 处理排序
  163. if (this.sortType === 'points_asc') {
  164. this.sortType = 'points_desc' // 积分降序
  165. this.extraParams['price'] = 0
  166. } else {
  167. this.sortType = 'points_asc' // 积分升序
  168. this.extraParams['price'] = 1
  169. }
  170. console.log('点击了兑换积分Tab,排序类型:', this.sortType)
  171. } else if (index === 2) {
  172. // 兑换量Tab - 处理排序
  173. if (this.sortType === 'exchange_asc') {
  174. this.sortType = 'exchange_desc' // 兑换量降序
  175. this.extraParams['sales'] = 0
  176. } else {
  177. this.sortType = 'exchange_asc' // 兑换量升序
  178. this.extraParams['sales'] = 1
  179. }
  180. console.log('点击了兑换量Tab,排序类型:', this.sortType)
  181. } else {
  182. // 商品分类Tab
  183. console.log('点击了商品分类Tab:', tabName, '分类ID:', categoryId)
  184. this.extraParams['categoryId'] = categoryId
  185. }
  186. this.initData()
  187. await this.getGoodsList({isRefresh : true})
  188. },
  189. onGoodsClick(item) {
  190. // 跳转到商品详情页
  191. uni.navigateTo({
  192. url: `/subPages/shop/goodsDetail?id=${item.id}`
  193. })
  194. },
  195. // 如何默认isRefresh为false
  196. async getGoodsList({isRefresh = false} = {}) {
  197. if (!this.hasMore) return
  198. if (this.title === undefined) this.title = ''
  199. const res = await this.$api.shop.queryGoodsList({
  200. pageNo: this.pageNo,
  201. pageSize: this.pageSize,
  202. title: this.title,
  203. ...this.extraParams
  204. })
  205. if (res.result.records.length) {
  206. if (isRefresh) {
  207. this.goodsList = res.result.records
  208. } else {
  209. this.goodsList.push(...res.result.records)
  210. }
  211. this.pageNo++
  212. }else {
  213. uni.showToast({
  214. title: '暂无商品',
  215. icon: 'none'
  216. })
  217. if (isRefresh) {
  218. this.goodsList = []
  219. }
  220. this.hasMore = false
  221. }
  222. },
  223. // 初始化请求参数
  224. initData() {
  225. this.pageNo = 1
  226. // this.goodsList = []
  227. this.hasMore = true
  228. }
  229. },
  230. async mounted() {
  231. // 确保store中的商品分类数据已加载
  232. if (this.categoryGoodsList.length === 0) {
  233. await this.$store.dispatch('getCategoryGoodsList')
  234. }
  235. // 初始化商品列表
  236. // this.getGoodsList()
  237. }
  238. }
  239. </script>
  240. <style lang="scss" scoped>
  241. .shop-content {
  242. background: #f8f8f8;
  243. min-height: calc(100vh - 400rpx);
  244. }
  245. .search-container {
  246. position: sticky;
  247. z-index: 999;
  248. top: 10rpx;
  249. padding: 15rpx 20rpx;
  250. background: #ffffff;
  251. }
  252. .tab-container {
  253. position: sticky;
  254. z-index: 999;
  255. top: 90rpx;
  256. background: #ffffff;
  257. border-bottom: 1rpx solid #f0f0f0;
  258. padding-bottom: 20rpx;
  259. .tab-scroll {
  260. white-space: nowrap;
  261. .tab-list {
  262. display: flex;
  263. padding: 0 30rpx;
  264. .tab-item {
  265. flex-shrink: 0;
  266. display: flex;
  267. align-items: center;
  268. padding: 24rpx 32rpx;
  269. margin-right: 16rpx;
  270. border-radius: 32rpx;
  271. background: #f8f9fa;
  272. transition: all 0.3s ease;
  273. .tab-text {
  274. font-size: 28rpx;
  275. color: #666666;
  276. font-weight: 500;
  277. }
  278. &.active {
  279. background: #218CDD;
  280. .tab-text {
  281. color: #ffffff;
  282. }
  283. .sort-arrows .arrow.active {
  284. color: #ffffff;
  285. }
  286. }
  287. &.sort-tab {
  288. .sort-arrows {
  289. margin-left: 8rpx;
  290. display: flex;
  291. flex-direction: column;
  292. align-items: center;
  293. .arrow {
  294. font-size: 16rpx;
  295. color: #cccccc;
  296. line-height: 1;
  297. transition: color 0.3s ease;
  298. &.up {
  299. margin-bottom: 2rpx;
  300. }
  301. &.active {
  302. color: rgb(64, 64, 64);
  303. }
  304. }
  305. }
  306. }
  307. }
  308. }
  309. }
  310. }
  311. .goods-container {
  312. padding: 20rpx 30rpx;
  313. background: #f8f8f8;
  314. }
  315. .goods-grid {
  316. display: grid;
  317. grid-template-columns: 1fr 1fr;
  318. gap: 20rpx;
  319. }
  320. .goods-item {
  321. display: flex;
  322. flex-direction: column;
  323. background: #ffffff;
  324. border-radius: 12rpx;
  325. padding: 20rpx;
  326. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
  327. border: 1rpx solid #f5f5f5;
  328. .goods-image {
  329. width: 100%;
  330. height: 230rpx;
  331. border-radius: 8rpx;
  332. overflow: hidden;
  333. margin-bottom: 16rpx;
  334. border: 2rpx dashed #e0e0e0;
  335. .image {
  336. width: 100%;
  337. height: 100%;
  338. object-fit: cover;
  339. }
  340. }
  341. .goods-info {
  342. flex: 1;
  343. display: flex;
  344. flex-direction: column;
  345. .goods-name {
  346. font-size: 28rpx;
  347. color: #333333;
  348. line-height: 1.4;
  349. margin-bottom: 16rpx;
  350. font-weight: 500;
  351. display: -webkit-box;
  352. -webkit-box-orient: vertical;
  353. -webkit-line-clamp: 2;
  354. overflow: hidden;
  355. min-height: 72rpx;
  356. }
  357. .goods-bottom {
  358. display: flex;
  359. // flex-direction: column;
  360. gap: 22rpx;
  361. margin-top: auto;
  362. .points-info {
  363. display: flex;
  364. align-items: center;
  365. .points-icon {
  366. width: 24rpx;
  367. height: 24rpx;
  368. margin-right: 6rpx;
  369. }
  370. .points-text {
  371. font-size: 28rpx;
  372. color: #218CDD;
  373. font-weight: 700;
  374. }
  375. }
  376. }
  377. }
  378. }
  379. </style>