homeIndex.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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.utils.checkLoginState()) this.getUserInfo()
  152. }
  153. if (option.hasOwnProperty('q') && option.q) {
  154. const url = decodeURIComponent(option.q)
  155. const obj = this.utils.urlToObj(url)
  156. uni.setStorageSync('userCode', obj.userCode)
  157. if (this.utils.checkLoginState()) 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.utils.checkLoginState()) 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. this.utils.isLogin().then(res => {
  339. if (res) getCartCount().then(cartRes => {
  340. if (cartRes.code == 200) this.cartCount = cartRes.data
  341. })
  342. })
  343. },
  344. getStoreConfig() {
  345. getStoreConfig().then(res => {
  346. if (res.code == 200) uni.setStorageSync('config', JSON.stringify(res.data))
  347. })
  348. },
  349. cancleTui() {
  350. this.tuiModalControl = false
  351. },
  352. submitTui(e) {
  353. if (!e.inputText) {
  354. uni.showToast({ icon: 'none', title: '请输入邀请码' })
  355. return
  356. }
  357. bindPromoter({ userCode: e.inputText }).then(res => {
  358. uni.showToast({ icon: 'none', title: res.msg })
  359. if (res.code == 200) this.tuiModalControl = false
  360. })
  361. },
  362. bindload() {},
  363. binderror() {},
  364. closeActivity() {
  365. this.activityShow = false
  366. },
  367. showActivity() {
  368. this.activityShow = false
  369. if (this.activity && this.activity.activityId) {
  370. uni.navigateTo({ url: '/pages_shopping/shopping/activityDetails?activityId=' + this.activity.activityId })
  371. }
  372. },
  373. menuClick(item) {
  374. const linkUrl = item.linkUrl || item.url
  375. const linkType = item.linkType != null ? item.linkType : (linkUrl ? 1 : 0)
  376. if (linkType == 1 && linkUrl) {
  377. if (linkUrl == '/pages/shopping/index' || linkUrl == '/pages/healthy/index') {
  378. uni.switchTab({ url: linkUrl })
  379. } else {
  380. uni.navigateTo({ url: linkUrl })
  381. }
  382. } else if (linkType == 0) {
  383. uni.showToast({ icon: 'none', title: '开发中...' })
  384. }
  385. },
  386. handleAdvClick(item) {
  387. if (item.showType == 1) {
  388. uni.setStorageSync('url', item.advUrl)
  389. uni.navigateTo({ url: 'h5' })
  390. } else if (item.showType == 2) {
  391. uni.navigateTo({ url: item.advUrl })
  392. } else if (item.showType == 3) {
  393. uni.setStorageSync('content', item.content)
  394. uni.navigateTo({ url: 'content' })
  395. }
  396. },
  397. goAuthUrl(url) {
  398. this.utils.isLogin().then(res => {
  399. if (res) uni.switchTab({ url })
  400. })
  401. },
  402. navTo(url) {
  403. uni.navigateTo({ url })
  404. },
  405. toSearch() {
  406. //uni.navigateTo({ url: './productSearch' })
  407. uni.navigateTo({ url: '/pages_mall/productList' })
  408. },
  409. showProduct(item,type) {
  410. //console.log('item',type)
  411. //uni.navigateTo({ url: '../shopping/productDetails?productId=' + item.productId })
  412. uni.navigateTo({ url: '/pages_mall/recommendList?type='+type})
  413. }
  414. }
  415. }
  416. </script>
  417. <style lang="scss" scoped>
  418. .tab-bar {
  419. height: 100rpx;
  420. background-color: #fff;
  421. display: flex;
  422. align-items: center;
  423. justify-content: space-around;
  424. border-top: 1rpx solid #eee;
  425. padding-bottom: constant(safe-area-inset-bottom);
  426. padding-bottom: env(safe-area-inset-bottom);
  427. position: fixed;
  428. bottom: 0;
  429. left: 0;
  430. right: 0;
  431. z-index: 9999;
  432. .tab-item {
  433. display: flex;
  434. flex-direction: column;
  435. align-items: center;
  436. justify-content: center;
  437. text {
  438. font-size: 24rpx;
  439. margin-top: 6rpx;
  440. color: #999999;
  441. }
  442. &.active text {
  443. color: #FF233C;
  444. }
  445. .icon-wrap {
  446. position: relative;
  447. .badge {
  448. position: absolute;
  449. top: -10rpx;
  450. right: -10rpx;
  451. background: #fa436a;
  452. color: #fff;
  453. font-size: 18rpx;
  454. width: 30rpx;
  455. height: 30rpx;
  456. border-radius: 50%;
  457. display: flex;
  458. align-items: center;
  459. justify-content: center;
  460. }
  461. }
  462. }
  463. }
  464. .content {
  465. width: 100%;
  466. position: relative;
  467. min-height: 0;
  468. padding-top: 1rpx;
  469. display: flex;
  470. flex-direction: column;
  471. height: 100%;
  472. }
  473. .scroll-content {
  474. flex: 1;
  475. height: 0;
  476. }
  477. .header-wrap {
  478. position: relative;
  479. z-index: 10;
  480. }
  481. .banner-row {
  482. position: relative;
  483. display: flex;
  484. gap: 20rpx;
  485. padding: 20rpx 24rpx;
  486. // background: #fff;
  487. }
  488. .banner-item {
  489. flex: 1;
  490. height: 220rpx;
  491. border-radius: 16rpx;
  492. overflow: hidden;
  493. background: #f5f5f5;
  494. }
  495. .banner-img {
  496. width: 100%;
  497. height: 100%;
  498. }
  499. .bg {
  500. width: 100%;
  501. height: 380rpx;
  502. position: absolute;
  503. top: 0;
  504. left: 0;
  505. z-index: 0;
  506. }
  507. .status_bar {
  508. width: 100%;
  509. }
  510. .banner-box {
  511. padding: 0 20upx;
  512. }
  513. .banner-box .inner {
  514. width: 100%;
  515. height: 236upx;
  516. border-radius: 10upx;
  517. overflow: hidden;
  518. }
  519. .banner-box .swiper,
  520. .banner-box .swiper-item,
  521. .banner-box .swiper-item image {
  522. width: 100%;
  523. height: 100%;
  524. }
  525. .menu-content {
  526. background-color: #fff;
  527. overflow: hidden;
  528. padding: 20upx 20upx 0;
  529. }
  530. .menu-box {
  531. display: flex;
  532. align-items: center;
  533. background-color: #fff;
  534. }
  535. .online-inquiry {
  536. box-sizing: border-box;
  537. width: 100%;
  538. height: 300upx;
  539. padding: 20upx;
  540. background: linear-gradient(180deg, rgba(255, 255, 255, 0.38) 62%, rgba(255, 255, 255, 0) 100%);
  541. }
  542. .online-inquiry .item {
  543. width: 100%;
  544. height: 100%;
  545. position: relative;
  546. }
  547. .online-inquiry .bg-img {
  548. width: 100%;
  549. height: 100%;
  550. position: absolute;
  551. top: 0;
  552. left: 0;
  553. border-radius: 15rpx;
  554. }
  555. .index-cont {
  556. box-sizing: border-box;
  557. padding: 0 20upx 120rpx;
  558. }
  559. .official-account {
  560. width: 100%;
  561. }
  562. .popup-box {
  563. position: fixed;
  564. top: 0;
  565. right: 0;
  566. left: 0;
  567. bottom: 0;
  568. z-index: 999;
  569. display: flex;
  570. justify-content: center;
  571. align-items: center;
  572. }
  573. .popup-box .info-mask {
  574. position: fixed;
  575. top: 0;
  576. right: 0;
  577. bottom: 0;
  578. left: 0;
  579. background-color: rgba(0, 0, 0, 0.5);
  580. z-index: 999;
  581. }
  582. .popup-box .info-form {
  583. z-index: 1000;
  584. width: 450rpx;
  585. display: flex;
  586. flex-direction: column;
  587. align-items: center;
  588. }
  589. .popup-box .info-form image {
  590. width: 100%;
  591. }
  592. </style>