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.

370 lines
9.4 KiB

3 years ago
  1. <template>
  2. <view class="app" :class="{overhidden: list.length === 0}">
  3. <!-- 头部 -->
  4. <page-header
  5. :keyword="keyword"
  6. :listType="listType"
  7. :sourcePage="sourcePage"
  8. @delKeyword="delKeyword"
  9. @changeListType="changeListType"
  10. ></page-header>
  11. <!-- 顶部筛选 分类栏 -->
  12. <view class="top" :style="{top: navigationBarHeight + statusBarHeight + 'px'}">
  13. <!-- 排序 -->
  14. <view class="sort-bar row">
  15. <view class="item row center" :class="{active: item.current, last: index === sortList.length-1}" v-for="(item,index) in sortList" :key="index" @click="changeSort(item)">
  16. <text>{{ item.name }}</text>
  17. <view v-if="item.isPrice" class="icon-wrap">
  18. <text class="mix-icon icon-down" :class="{active: item.type === 3}"></text>
  19. <text class="mix-icon icon-arrow-top" :class="{active: item.type === 4}"></text>
  20. </view>
  21. </view>
  22. <!-- #ifdef MP -->
  23. <view class="btn center">
  24. <text
  25. class="mix-icon"
  26. :class="listType === 'column' ? 'icon-hengxiangliebiao' : 'icon-shuxiangliebiao'"
  27. @click="changeListType"
  28. ></text>
  29. </view>
  30. <!-- #endif -->
  31. </view>
  32. <!-- 商品分类 -->
  33. <scroll-view v-if="curCateItem.parent_id" class="cate-bar b-b" scroll-x :scroll-left="curCateItem.left ? curCateItem.left: 0" scroll-with-animation >
  34. <view class="cate-wrap row">
  35. <view class="fill-view"></view>
  36. <text :id="'cate-'+item._id" class="item" :class="{active: item._id === curCateItem._id}" v-for="item in cateList" :key="item._id" @click="changeCategory(item)">{{ item.name }}</text>
  37. <view class="fill-view"></view>
  38. </view>
  39. </scroll-view>
  40. </view>
  41. <!-- 商品分类的占位栏 -->
  42. <view v-if="curCateItem.parent_id" style="min-height: 94rpx"></view>
  43. <!-- 产品列表 -->
  44. <mescroll-body
  45. ref="mescrollRef"
  46. @init="mescrollInit"
  47. :top="headerHeight"
  48. @down="downCallback"
  49. :up="upOption"
  50. @up="loadList"
  51. >
  52. <product-list ref="productList" :list="list" :listType="listType"></product-list>
  53. </mescroll-body>
  54. <mix-loading v-if="isLoading"></mix-loading>
  55. </view>
  56. </template>
  57. <script>
  58. import pageHeader from './components/list-page-header'
  59. import MescrollBody from "@/components/mescroll-uni/mescroll-body.vue"
  60. import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
  61. import productList from './components/product-list'
  62. export default {
  63. components: {
  64. pageHeader,
  65. MescrollBody,
  66. productList
  67. },
  68. mixins: [MescrollMixin],
  69. data() {
  70. return {
  71. sourcePage: '', //来源页 主要用来判断是否来自搜索页
  72. listType: 'column', //列表类型 column竖向列表 row 横向列表
  73. sortList: [
  74. {name: '综合排序', type: 1, current: true},
  75. {name: '销量', type: 2, current: false},
  76. {name: '价格', type: 3, isPrice: true, current: false},
  77. {name: '好评率', type: 5, current: false}
  78. ],
  79. isHot: 0,//热门推荐
  80. keyword: '',
  81. cateList: [], //分类列表
  82. curCateItem: {}, //当前分类
  83. list: [], //商品列表
  84. upOption:{
  85. auto: false, // 不自动加载
  86. page: {
  87. num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
  88. size: 10 // 每页数据的数量
  89. },
  90. noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
  91. },
  92. }
  93. },
  94. computed: {
  95. statusBarHeight(){
  96. return this.systemInfo.statusBarHeight
  97. },
  98. navigationBarHeight(){
  99. return this.systemInfo.navigationBarHeight;
  100. },
  101. headerHeight(){
  102. return 750 / uni.upx2px(750) * (this.navigationBarHeight + this.statusBarHeight) + 96;
  103. },
  104. },
  105. onLoad(options) {
  106. this.isLoading = true;
  107. this.sourcePage = options.sourcePage || '';
  108. this.keyword = options.keyword || ''; //搜索关键字
  109. this.prePage = options.prePage; //来源页 主要用于区分是否来自搜索页面
  110. //分类
  111. if(options.firstCateId){
  112. this.curCateItem = {
  113. _id: options.cateId || '',
  114. parent_id: options.firstCateId
  115. }
  116. this.loadCateList();
  117. }
  118. //热门推荐
  119. this.isHot = options.isHot || 0;
  120. },
  121. onReady() {
  122. this.calcCateRect();
  123. },
  124. methods: {
  125. //加载产品列表
  126. async loadList(e){
  127. this.mescroll.removeEmpty();
  128. const {sortList, curCateItem, keyword} = this;
  129. const data = {
  130. sort_type: sortList.filter(item=> item.current)[0].type,
  131. cate_id: curCateItem._id || '',
  132. first_cate_id: curCateItem.parent_id || '',
  133. keyword,
  134. is_hot: this.isHot,
  135. offset: (e.num - 1) * e.size,
  136. limit: e.size,
  137. }
  138. this.$refs.productList.loadType = e.num === 1 ? 'refresh' : 'add';
  139. const res = await this.$request('product', 'getList', data);
  140. const curList = res.data;
  141. if(e.num === 1){
  142. //第一页清空数据重载
  143. this.list = [];
  144. this.loaded && curList.forEach(item=> {item.loaded = true;})
  145. if(curList.length > 0){
  146. uni.pageScrollTo({
  147. scrollTop: 0,
  148. duration: 200
  149. })
  150. }
  151. }
  152. this.list = this.list.concat(curList); //追加新数据
  153. this.mescroll.endSuccess(curList.length); //结束加载状态
  154. },
  155. //加载分类列表
  156. async loadCateList(){
  157. const res = await this.$request('product', 'getSecondCategory', {
  158. id: this.curCateItem.parent_id
  159. }, {
  160. changeLoaded: false
  161. })
  162. this.cateList = res.data || [];
  163. this.$nextTick(()=>{
  164. this.calcCateRect();
  165. })
  166. },
  167. mescrollInit(mescroll){
  168. this.mescroll = mescroll;
  169. setTimeout(()=>{
  170. this.refreshList();
  171. }, 10)
  172. },
  173. //刷新列表
  174. refreshList(){
  175. this.mescroll.resetUpScroll(false)
  176. this.isLoading = true;
  177. },
  178. //分类默认 处理分类滚动
  179. calcCateRect(){
  180. const tasks = [];
  181. this.cateList.forEach(async item=> {
  182. tasks.push(new Promise(resolve => {
  183. uni.createSelectorQuery().select(`#cate-${item._id}`).boundingClientRect(data => {
  184. item.left = data.left - (uni.upx2px(375) - data.width/2);
  185. resolve();
  186. }).exec()
  187. }))
  188. })
  189. Promise.all(tasks).then(()=>{
  190. let cur = this.cateList.filter(item=> item._id === this.curCateItem._id);
  191. if(cur.length > 0){
  192. this.curCateItem = cur[0];
  193. }
  194. })
  195. },
  196. //分类选择
  197. changeCategory(item){
  198. if(this.curCateItem._id === item._id){
  199. return;
  200. }
  201. this.curCateItem = item;
  202. this.refreshList();
  203. },
  204. //排序
  205. changeSort(item){
  206. if(item.current){
  207. if(item.isPrice){
  208. item.type = item.type === 3 ? 4 : 3;
  209. }else{
  210. return;
  211. }
  212. }else{
  213. this.sortList.forEach(v=> v.current = false);
  214. item.current = true;
  215. if(item.type === 3 || item.type === 4){
  216. item.type = 3;
  217. }
  218. }
  219. this.refreshList();
  220. },
  221. //删除搜索关键字
  222. delKeyword(){
  223. this.keyword = '';
  224. this.refreshList();
  225. },
  226. //修改列表显示方式
  227. changeListType(){
  228. this.listType = this.listType === 'column' ? 'row' : 'column';
  229. }
  230. }
  231. }
  232. </script>
  233. <style>
  234. page{
  235. background-color: #f8f8f8;
  236. }
  237. </style>
  238. <style scoped lang="scss">
  239. .app{
  240. /* #ifndef MP */
  241. &.overhidden{
  242. height: 100vh;
  243. overflow: hidden;
  244. }
  245. /* #endif */
  246. }
  247. /deep/ .mix-empty .default{
  248. padding-top: 38vh;
  249. /* #ifdef H5 */
  250. padding-top: 34vh;
  251. /* #endif */
  252. }
  253. .top{
  254. position: fixed;
  255. left: 0;
  256. width: 100%;
  257. z-index: 95;
  258. background-color: #fff;
  259. }
  260. .sort-bar{
  261. justify-content: space-around;
  262. height: 76rpx;
  263. padding: 4rpx 0 4rpx 4rpx;
  264. /* #ifdef MP */
  265. padding-left: 10rpx;
  266. /* #endif */
  267. background-color: #fff;
  268. position: relative;
  269. z-index: 1;
  270. .item{
  271. flex: 1;
  272. height: 100%;
  273. font-size: 28rpx;
  274. color: #333;
  275. position: relative;
  276. overflow: hidden;
  277. &.active{
  278. color: $base-color;
  279. font-weight: 700;
  280. &:after{
  281. position: absolute;
  282. left: 50%;
  283. bottom: 0;
  284. transform: translateX(-28rpx);
  285. content: '';
  286. width: 56rpx;
  287. height: 4rpx;
  288. background-color: $base-color;
  289. border-radius: 10px;
  290. }
  291. .mix-icon.active{
  292. color: $base-color;
  293. }
  294. }
  295. /* #ifdef MP */
  296. &.last:before{
  297. content: '';
  298. position: absolute;
  299. right: 0;
  300. top: 50%;
  301. transform: translateY(-50%);
  302. width: 2rpx;
  303. height: 40rpx;
  304. box-shadow: 0 0 16rpx rgba(0,0,0,.6);
  305. }
  306. /* #endif */
  307. }
  308. .icon-wrap{
  309. display: flex;
  310. flex-direction: column;
  311. padding-left: 8rpx;
  312. }
  313. .mix-icon{
  314. font-size: 14rpx;
  315. color: #bbb;
  316. }
  317. .btn{
  318. height: 68rpx;
  319. padding-left: 16rpx;
  320. padding-right: 20rpx;
  321. .icon-hengxiangliebiao, .icon-shuxiangliebiao{
  322. font-size: 40rpx;
  323. color: #333;
  324. }
  325. }
  326. }
  327. .cate-bar:after{
  328. border-color: #f5f5f5;
  329. }
  330. .cate-wrap{
  331. flex-wrap: nowrap;
  332. height: 96rpx;
  333. padding-bottom: 4rpx;
  334. .fill-view{
  335. flex-shrink: 0;
  336. width: 20rpx;
  337. height: 20rpx;
  338. &:last-child{
  339. width: 10rpx;
  340. }
  341. }
  342. .item{
  343. flex-shrink: 0;
  344. height: 58rpx;
  345. padding: 0 28rpx;
  346. margin-right: 20rpx;
  347. font-size: 22rpx;
  348. color: #333;
  349. text-align: center;
  350. line-height: 58rpx;
  351. background-color: #f5f5f5;
  352. border-radius: 100px;
  353. }
  354. .active{
  355. color: $base-color;
  356. background-color: #fff8f4;
  357. font-weight: 700;
  358. }
  359. }
  360. </style>