homeIndex.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. <template>
  2. <view class="content">
  3. <image class="bg" src="@/static/images/bg.png" mode="aspectFill"></image>
  4. <view class="header-wrap">
  5. <SearchBar
  6. :margin-top="'24rpx'"
  7. :bg-color="bgColor"
  8. :status-bar-height="statusBarHeight"
  9. :cart-count="cartCount"
  10. :search-width="'100%'"
  11. placeholder="搜索商品"
  12. @searchClick="toSearch"
  13. @cartClick="goAuthUrl()"
  14. />
  15. </view>
  16. <!-- 作为组件嵌入 indexOld 时,外层使用 overflow:hidden;这里改成 scroll-view 保障可滚动与触底加载 -->
  17. <scroll-view scroll-y class="scroll-content" @scroll="onScroll" @scrolltolower="loadMoreGoods">
  18. <!-- 顶部占位:避免固定搜索栏遮挡 -->
  19. <view style="padding-bottom:110rpx">
  20. <view class="status_bar" :style="{height: statusBarHeight}"></view>
  21. </view>
  22. <!-- 推荐频道 -->
  23. <ChannelEntry :list="channelList" :per-row="4" :rows="2" @click="onChannelClick" />
  24. <!-- 金刚区:分类图标 2 行 4 列,仅展示 -->
  25. <CategoryTags :tags="categoryTagsData" @selectClick="onCategoryTagsSelect" />
  26. <!-- 推荐区块:左侧直播卡(直播中/回放)+ 右上绿色有机 + 右下上新推荐 -->
  27. <RecommendSection
  28. :live="recommendData.live"
  29. :green="recommendData.green"
  30. :hot="recommendData.hot"
  31. @liveClick="onLiveClick"
  32. @more="onRecommendMore"
  33. @itemClick="onRecommendItemClick"
  34. />
  35. <!-- 瀑布流:全部 + 分类标签横向滚动 -->
  36. <GoodsNav :nav-list="goodsNavList" :active-id="activeGoodsNavId" @select="onGoodsNavSelect" />
  37. <GoodsList
  38. :list="goodsList"
  39. :loading="goodsLoading"
  40. :has-more="goodsHasMore"
  41. @itemClick="showProduct"
  42. @loadMore="loadMoreGoods"
  43. />
  44. <!--#ifdef MP-WEIXIN-->
  45. <view class="official-account">
  46. <official-account @load="bindload" @error="binderror"></official-account>
  47. </view>
  48. <!--#endif-->
  49. <view class="popup-box" v-if="activityShow">
  50. <view class="info-mask" @tap="closeActivity()"></view>
  51. <view class="info-form">
  52. <image :src="activity.logoUrl" @tap="showActivity()" />
  53. </view>
  54. </view>
  55. <!-- <Server /> -->
  56. </scroll-view>
  57. </view>
  58. </template>
  59. <script>
  60. // import zModal from '@/components/z-modal/z-modal.vue'
  61. import { getMenu, getIndexData, getCartCount } from '@/api/index'
  62. import { getStoreConfig } from '@/api/common'
  63. import { getHomeInit, getHomeRecommend, getHomeGoods } from '@/api/home.js'
  64. import HotProduct from './components/HotProduct.vue'
  65. //import TuiProduct from '@/components/tuiProduct.vue'
  66. import SearchBar from './components/SearchBar.vue'
  67. import CategoryTags from './components/CategoryTags.vue'
  68. import ChannelEntry from './components/ChannelEntry.vue'
  69. import RecommendSection from './components/RecommendSection.vue'
  70. import GoodsNav from './components/GoodsNav.vue'
  71. import GoodsList from './components/GoodsList.vue'
  72. import { getUserInfo, bindPromoter } from '@/api/user'
  73. export default {
  74. components: {
  75. // zModal,
  76. HotProduct,
  77. //TuiProduct,
  78. SearchBar,
  79. CategoryTags,
  80. ChannelEntry,
  81. RecommendSection,
  82. GoodsNav,
  83. GoodsList
  84. },
  85. data() {
  86. return {
  87. btnGroup: [
  88. { text: '取消', color: '#FFFFFF', bgColor: '#999999', width: '150rpx', height: '80rpx', shape: 'fillet', eventName: 'cancle' },
  89. { text: '确定', color: '#FFFFFF', bgColor: '#FF233C', width: '150rpx', height: '80rpx', shape: 'fillet', eventName: 'sure' }
  90. ],
  91. menuButtonInfo: {
  92. top: '0px',
  93. height: '0px',
  94. centerY: '0px',
  95. right: '0px'
  96. }, // 胶囊按钮布局信息
  97. initDone: false,
  98. showInitRan: false,
  99. tuiModalControl: false,
  100. activity: null,
  101. activityShow: false,
  102. hotProductList: [],
  103. menus: [],
  104. channelList: [],
  105. categoryTagsData: [],
  106. goodsNav: [],
  107. recommendData: { live: [], green: [], hot: [] },
  108. activeGoodsNavId: 'all',
  109. goodsList: [],
  110. goodsLoading: false,
  111. goodsHasMore: true,
  112. goodsPageNum: 1,
  113. cartCount: 0,
  114. advList: [],
  115. top: 0,
  116. statusBarHeight: uni.getSystemInfoSync().statusBarHeight + 'px',
  117. userinfoa: [],
  118. isuser: false,
  119. currentTab: 0
  120. }
  121. },
  122. computed: {
  123. bgColor() {
  124. const t = this.top / 30
  125. if(t>0){
  126. return '/static/images/bg.png'
  127. }else{
  128. return ''
  129. }
  130. },
  131. // 瀑布流标签:后端 categoryTags(不包含“全部”)
  132. goodsNavList() {
  133. const tags = (this.goodsNav || []).map(t => ({
  134. id: t.id || t.cateId,
  135. name: t.categoryName || t.cateName || t.name,
  136. categoryName: t.categoryName || t.cateName || t.name
  137. }))
  138. return tags
  139. }
  140. },
  141. mounted() {
  142. // 作为组件嵌入 indexOld 时,onShow/onLoad 通常不会触发;这里兜底初始化数据
  143. setTimeout(() => {
  144. if (!this.showInitRan && !this.initDone) this.initIndexData(false)
  145. }, 30)
  146. },
  147. onLoad(option) {
  148. this.getMenuButtonInfo(); // 初始化获取胶囊信息
  149. if (option.userCode != null) {
  150. uni.setStorageSync('userCode', option.userCode)
  151. if (this.$isLogin()) this.getUserInfo()
  152. }
  153. if (option.hasOwnProperty('q') && option.q) {
  154. const url = decodeURIComponent(option.q)
  155. const obj = this.$urlToObj(url)
  156. uni.setStorageSync('userCode', obj.userCode)
  157. if (this.$isLogin()) this.getUserInfo()
  158. }
  159. },
  160. onUnload() {
  161. uni.showTabBar();
  162. uni.$emit('stop')
  163. },
  164. onHide() {
  165. uni.$emit('stop')
  166. },
  167. onBackPress() {
  168. uni.showTabBar();
  169. uni.switchTab({
  170. //url: '/pages_im/pages/conversation/conversationList/index'
  171. url: '../index/index'
  172. })
  173. },
  174. onPageScroll(e) {
  175. // 页面滚动时兼容 uni-app 事件结构
  176. this.top = e?.detail?.scrollTop ?? e?.scrollTop ?? 0
  177. },
  178. onShow() {
  179. this.showInitRan = true
  180. this.initIndexData(true)
  181. uni.hideTabBar();
  182. },
  183. onShareAppMessage() {
  184. return { title: '岚财优选-您的专属解决方案', path: '/pages/common/launch', imageUrl: '/static/logo.jpg' }
  185. },
  186. onShareTimeline() {
  187. return { title: '岚财优选-您的专属解决方案', query: '', imageUrl: '/static/logo.jpg' }
  188. },
  189. onReachBottom() {
  190. this.loadMoreGoods()
  191. //this.$refs.tuiProduct && this.$refs.tuiProduct.getTuiProducts()
  192. },
  193. methods: {
  194. onScroll(e) {
  195. // scroll-view 的滚动事件
  196. this.top = e?.detail?.scrollTop ?? e?.scrollTop ?? 0
  197. },
  198. switchTab(index) {
  199. this.currentTab = index;
  200. },
  201. initIndexData(force = false) {
  202. if (this.initDone && !force) return
  203. this.initDone = true
  204. //this.getMenuButtonInfo()
  205. this.getHomeInit()
  206. this.getHomeRecommend()
  207. this.getMenu()
  208. //this.getIndexData()
  209. if (uni.getStorageSync('AppToken')) {
  210. this.getUserInfo()
  211. } else {
  212. this.isuser = true
  213. }
  214. if (this.$isLogin()) this.getCartCount()
  215. this.getStoreConfig()
  216. },
  217. // 获取胶囊按钮布局参数
  218. getMenuButtonInfo() {
  219. // 微信小程序API(Uniapp可直接用uni.getMenuButtonBoundingClientRect)
  220. const menuBtn = uni.getMenuButtonBoundingClientRect();
  221. if (menuBtn) {
  222. this.menuButtonInfo = {
  223. top: menuBtn.top + 'px', // 胶囊顶部距离
  224. height: menuBtn.height + 'px', // 胶囊高度
  225. centerY: (menuBtn.top + menuBtn.height / 2) + 'px', // 胶囊垂直居中Y坐标
  226. right: menuBtn.right + 'px' // 胶囊右侧距离
  227. };
  228. }
  229. },
  230. getHomeInit() {
  231. getHomeInit().then(res => {
  232. if (res.code == 200 && res.data) {
  233. this.channelList = res.data.channelList || []
  234. this.categoryTagsData = res.data.categoryTags || []
  235. this.goodsNav = res.data.goodsNav || []
  236. if (this.activeGoodsNavId === null || this.activeGoodsNavId === undefined) {
  237. this.activeGoodsNavId = 'all'
  238. }
  239. this.fetchGoodsList(true)
  240. }
  241. }).catch(() => {})
  242. },
  243. getHomeRecommend() {
  244. getHomeRecommend().then(res => {
  245. if (res.code != 200) return
  246. this.recommendData = {
  247. live: res.live && Array.isArray(res.live) ? res.live.slice(0, 3) : [],
  248. green: res.green && Array.isArray(res.green) ? res.green : [],
  249. hot: res.hot && Array.isArray(res.hot) ? res.hot : []
  250. }
  251. }).catch(() => {})
  252. },
  253. fetchGoodsList(reset) {
  254. if (reset) {
  255. this.goodsPageNum = 1
  256. this.goodsList = []
  257. this.goodsHasMore = true
  258. }
  259. if (this.goodsLoading || !this.goodsHasMore) return
  260. this.goodsLoading = true
  261. const id = this.activeGoodsNavId === 'all' || this.activeGoodsNavId === '' ? 0 : this.activeGoodsNavId
  262. getHomeGoods({ id, position:2, pageNum: this.goodsPageNum, pageSize: 10 }).then(res => {
  263. this.goodsLoading = false
  264. if (res.code == 200 && res.data) {
  265. const list = Array.isArray(res.data.list) ? res.data.list : []
  266. const total = res.data.total != null ? Number(res.data.total) : 0
  267. if (reset) this.goodsList = list
  268. else this.goodsList = this.goodsList.concat(list)
  269. this.goodsHasMore = list.length >= 20 && (this.goodsList.length < total || total === 0)
  270. this.goodsPageNum++
  271. }
  272. }).catch(() => { this.goodsLoading = false })
  273. },
  274. loadMoreGoods() {
  275. this.fetchGoodsList(false)
  276. },
  277. onGoodsNavSelect(item) {
  278. //console.log("2222")
  279. //console.log(item.id)
  280. this.activeGoodsNavId = item.id
  281. this.fetchGoodsList(true)
  282. },
  283. onCategoryTagsSelect(item) {
  284. //console.log(this.categoryTagsData,'item')
  285. // 金刚区点击:带分类 id 跳转商品列表页筛选(防护 item 为空)
  286. if (!item || typeof item !== 'object') {
  287. uni.navigateTo({ url: '/pages_mall/productList' })
  288. return
  289. }
  290. const id = item.id ?? item.cateId ?? item.categoryId
  291. if (id != null && id !== '') {
  292. uni.navigateTo({ url: '/pages_mall/productList?id=' + id })
  293. } else {
  294. uni.navigateTo({ url: '/pages_mall/productList' })
  295. }
  296. },
  297. onChannelClick(item) {
  298. // 推荐频道点击,可跳转或筛选,按需扩展
  299. },
  300. onRecommendMore(section) {
  301. if (section && section.moreUrl) {
  302. uni.navigateTo({ url: '/pages_mall/recommendList' })
  303. return
  304. }
  305. if (section && (section.type === 'green' || section.type === 'hot')) {
  306. uni.navigateTo({ url: '/pages_mall/recommendList?type='+section.type})
  307. }
  308. },
  309. onLiveClick(item) {
  310. //console.log(item)
  311. if (item && item.liveId) uni.navigateTo({ url: '/pages_course/living?liveId=' + item.liveId })
  312. },
  313. onRecommendItemClick(item,type) {
  314. if (item.productId) this.showProduct(item,type)
  315. },
  316. getMenu() {
  317. // getMenu().then(res => {
  318. // if (res.code == 200) this.menus = res.data || []
  319. // })
  320. },
  321. getIndexData() {
  322. getIndexData({}).then(res => {
  323. if (res.code == 200) {
  324. this.advList = res.data.advList || []
  325. this.hotProductList = res.data.hotProductList || []
  326. } else {
  327. uni.showToast({ icon: 'none', title: '请求失败' })
  328. }
  329. })
  330. },
  331. getUserInfo() {
  332. getUserInfo().then(res => {
  333. if (res.code == 200 && res.user != null) this.userinfoa = res.user
  334. else uni.showToast({ icon: 'none', title: '请求失败' })
  335. })
  336. },
  337. getCartCount() {
  338. if (this.$isLogin()){
  339. getCartCount().then(cartRes => {
  340. if (cartRes.code == 200) this.cartCount = cartRes.data
  341. this.$emit('updateMallIndexCartCount', cartRes.data || 0)
  342. })
  343. }
  344. },
  345. getStoreConfig() {
  346. getStoreConfig().then(res => {
  347. if (res.code == 200) uni.setStorageSync('config', JSON.stringify(res.data))
  348. })
  349. },
  350. cancleTui() {
  351. this.tuiModalControl = false
  352. },
  353. submitTui(e) {
  354. if (!e.inputText) {
  355. uni.showToast({ icon: 'none', title: '请输入邀请码' })
  356. return
  357. }
  358. bindPromoter({ userCode: e.inputText }).then(res => {
  359. uni.showToast({ icon: 'none', title: res.msg })
  360. if (res.code == 200) this.tuiModalControl = false
  361. })
  362. },
  363. bindload() {},
  364. binderror() {},
  365. closeActivity() {
  366. this.activityShow = false
  367. },
  368. showActivity() {
  369. this.activityShow = false
  370. if (this.activity && this.activity.activityId) {
  371. uni.navigateTo({ url: '/pages_shopping/shopping/activityDetails?activityId=' + this.activity.activityId })
  372. }
  373. },
  374. menuClick(item) {
  375. const linkUrl = item.linkUrl || item.url
  376. const linkType = item.linkType != null ? item.linkType : (linkUrl ? 1 : 0)
  377. if (linkType == 1 && linkUrl) {
  378. if (linkUrl == '/pages/shopping/index' || linkUrl == '/pages/healthy/index') {
  379. uni.switchTab({ url: linkUrl })
  380. } else {
  381. uni.navigateTo({ url: linkUrl })
  382. }
  383. } else if (linkType == 0) {
  384. uni.showToast({ icon: 'none', title: '开发中...' })
  385. }
  386. },
  387. handleAdvClick(item) {
  388. if (item.showType == 1) {
  389. uni.setStorageSync('url', item.advUrl)
  390. uni.navigateTo({ url: 'h5' })
  391. } else if (item.showType == 2) {
  392. uni.navigateTo({ url: item.advUrl })
  393. } else if (item.showType == 3) {
  394. uni.setStorageSync('content', item.content)
  395. uni.navigateTo({ url: 'content' })
  396. }
  397. },
  398. goAuthUrl(url) {
  399. if (this.$isLogin()) {
  400. uni.switchTab({ url })
  401. }
  402. },
  403. navTo(url) {
  404. uni.navigateTo({ url })
  405. },
  406. toSearch() {
  407. //uni.navigateTo({ url: './productSearch' })
  408. uni.navigateTo({ url: '/pages_mall/productList' })
  409. },
  410. showProduct(item,type) {
  411. //console.log('item',type)
  412. //uni.navigateTo({ url: '../shopping/productDetails?productId=' + item.productId })
  413. uni.navigateTo({ url: '/pages_mall/recommendList?type='+type})
  414. }
  415. }
  416. }
  417. </script>
  418. <style lang="scss" scoped>
  419. .tab-bar {
  420. height: 100rpx;
  421. background-color: #fff;
  422. display: flex;
  423. align-items: center;
  424. justify-content: space-around;
  425. border-top: 1rpx solid #eee;
  426. padding-bottom: constant(safe-area-inset-bottom);
  427. padding-bottom: env(safe-area-inset-bottom);
  428. position: fixed;
  429. bottom: 0;
  430. left: 0;
  431. right: 0;
  432. z-index: 9999;
  433. .tab-item {
  434. display: flex;
  435. flex-direction: column;
  436. align-items: center;
  437. justify-content: center;
  438. text {
  439. font-size: 24rpx;
  440. margin-top: 6rpx;
  441. color: #999999;
  442. }
  443. &.active text {
  444. color: #FF233C;
  445. }
  446. .icon-wrap {
  447. position: relative;
  448. .badge {
  449. position: absolute;
  450. top: -10rpx;
  451. right: -10rpx;
  452. background: #fa436a;
  453. color: #fff;
  454. font-size: 18rpx;
  455. width: 30rpx;
  456. height: 30rpx;
  457. border-radius: 50%;
  458. display: flex;
  459. align-items: center;
  460. justify-content: center;
  461. }
  462. }
  463. }
  464. }
  465. .content {
  466. width: 100%;
  467. position: relative;
  468. min-height: 0;
  469. padding-top: 1rpx;
  470. display: flex;
  471. flex-direction: column;
  472. height: 100%;
  473. }
  474. .scroll-content {
  475. flex: 1;
  476. height: 0;
  477. }
  478. .header-wrap {
  479. position: relative;
  480. z-index: 10;
  481. }
  482. .banner-row {
  483. position: relative;
  484. display: flex;
  485. gap: 20rpx;
  486. padding: 20rpx 24rpx;
  487. // background: #fff;
  488. }
  489. .banner-item {
  490. flex: 1;
  491. height: 220rpx;
  492. border-radius: 16rpx;
  493. overflow: hidden;
  494. background: #f5f5f5;
  495. }
  496. .banner-img {
  497. width: 100%;
  498. height: 100%;
  499. }
  500. .bg {
  501. width: 100%;
  502. height: 380rpx;
  503. position: absolute;
  504. top: 0;
  505. left: 0;
  506. z-index: 0;
  507. }
  508. .status_bar {
  509. width: 100%;
  510. }
  511. .banner-box {
  512. padding: 0 20upx;
  513. }
  514. .banner-box .inner {
  515. width: 100%;
  516. height: 236upx;
  517. border-radius: 10upx;
  518. overflow: hidden;
  519. }
  520. .banner-box .swiper,
  521. .banner-box .swiper-item,
  522. .banner-box .swiper-item image {
  523. width: 100%;
  524. height: 100%;
  525. }
  526. .menu-content {
  527. background-color: #fff;
  528. overflow: hidden;
  529. padding: 20upx 20upx 0;
  530. }
  531. .menu-box {
  532. display: flex;
  533. align-items: center;
  534. background-color: #fff;
  535. }
  536. .online-inquiry {
  537. box-sizing: border-box;
  538. width: 100%;
  539. height: 300upx;
  540. padding: 20upx;
  541. background: linear-gradient(180deg, rgba(255, 255, 255, 0.38) 62%, rgba(255, 255, 255, 0) 100%);
  542. }
  543. .online-inquiry .item {
  544. width: 100%;
  545. height: 100%;
  546. position: relative;
  547. }
  548. .online-inquiry .bg-img {
  549. width: 100%;
  550. height: 100%;
  551. position: absolute;
  552. top: 0;
  553. left: 0;
  554. border-radius: 15rpx;
  555. }
  556. .index-cont {
  557. box-sizing: border-box;
  558. padding: 0 20upx 120rpx;
  559. }
  560. .official-account {
  561. width: 100%;
  562. }
  563. .popup-box {
  564. position: fixed;
  565. top: 0;
  566. right: 0;
  567. left: 0;
  568. bottom: 0;
  569. z-index: 999;
  570. display: flex;
  571. justify-content: center;
  572. align-items: center;
  573. }
  574. .popup-box .info-mask {
  575. position: fixed;
  576. top: 0;
  577. right: 0;
  578. bottom: 0;
  579. left: 0;
  580. background-color: rgba(0, 0, 0, 0.5);
  581. z-index: 999;
  582. }
  583. .popup-box .info-form {
  584. z-index: 1000;
  585. width: 450rpx;
  586. display: flex;
  587. flex-direction: column;
  588. align-items: center;
  589. }
  590. .popup-box .info-form image {
  591. width: 100%;
  592. }
  593. </style>