692 lines
26 KiB
Vue
692 lines
26 KiB
Vue
<template>
|
||
<view class="home2" v-if="loading">
|
||
<!-- ===== 顶部红色头区(状态栏 + 定位 + 搜索 + 分类条) ===== -->
|
||
<view class="hd" :style="{ paddingTop: top + 'px' }">
|
||
<!-- 定位 -->
|
||
<view class="hd-loc" @tap="map">
|
||
<image class="hd-loc-icon" src="https://static.hshuishang.com/Index_add.png" mode="widthFix" />
|
||
<text class="hd-loc-txt">{{ currentCity.cityName || '选择城市' }}</text>
|
||
<text class="hd-loc-arrow">▾</text>
|
||
</view>
|
||
|
||
<!-- 搜索框(新增,本轮仅 UI + 占位,后台好店搜索接口回头接入 handleSearch) -->
|
||
<view class="hd-search">
|
||
<image class="hd-search-icon"
|
||
src="https://static.hshuishang.com/property-img-file/com_communitySearchIcon.png" mode="aspectFill" />
|
||
<input class="hd-search-input" v-model="keyword" :placeholder="searchPlaceholder"
|
||
confirm-type="search" @confirm="handleSearch" />
|
||
<view class="hd-search-btn" @tap="handleSearch">搜索</view>
|
||
</view>
|
||
|
||
</view>
|
||
|
||
<!-- ===== 主体(白底圆角上浮) ===== -->
|
||
<view class="bd">
|
||
<!-- 轮播图 -->
|
||
<view class="banner" v-if="isRecommend && bannerList.length">
|
||
<swiper class="banner-swiper" @animationfinish="swipers" autoplay circular>
|
||
<swiper-item v-for="(item, index) in bannerList" :key="index" @click="handleAdClick(item)">
|
||
<image class="banner-img" :src="item.pic_src" mode="aspectFill" />
|
||
</swiper-item>
|
||
</swiper>
|
||
<view class="banner-dots">
|
||
<view :class="['dot', currentIdx == index ? 'on' : '']" v-for="(item, index) in bannerList" :key="index">
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 金刚区(自绘 flex 布局,间距完全可控,不用 u-grid 以免组件自带底部空间) -->
|
||
<view class="grid" v-if="isRecommend && tabList.length">
|
||
<view class="grid-item" :style="{ width: (100 / (rowNum || 5)) + '%' }"
|
||
v-for="(item, index) in tabList" :key="index" @tap="headerServerClick(item)">
|
||
<image class="grid-icon" :src="item.icon_src" mode="aspectFill"></image>
|
||
<text class="grid-txt">{{ item.title }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 广告栏:固定三格(左大 + 右上 + 右下),空位显示招商占位 -->
|
||
<view class="ads" v-if="isRecommend">
|
||
<view class="ads-left">
|
||
<image v-if="isRealAd(serverLeftList[0])" :src="serverLeft" mode="aspectFill"
|
||
@tap="handleAdClick(serverLeftList[0])" />
|
||
<view v-else class="ad-ph">广告招商中</view>
|
||
</view>
|
||
<view class="ads-right">
|
||
<view class="ads-right-item" v-for="(item, index) in serverRightList" :key="index">
|
||
<image v-if="isRealAd(item)" :src="item.pic_src" mode="aspectFill" @tap="handleAdClick(item)" />
|
||
<view v-else class="ad-ph">广告招商中</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 好店列表标题 -->
|
||
<view class="wf-title" v-if="isRecommend">
|
||
<!--view class="wf-title-main">
|
||
<text class="wf-title-loc" v-if="currentCity.district">{{ currentCity.district }}</text>
|
||
附近好店
|
||
</view>
|
||
<view class="wf-title-tag">一刻钟便利生活圈</view-->
|
||
</view>
|
||
|
||
<!-- 分类条:移到瀑布上方,上滑时吸顶(top=页头高度),仅瀑布在其下滚动 -->
|
||
<scroll-view scroll-x class="hd-cate" v-if="lifeCircleList.length" :style="{ top: cateTop + 'px' }">
|
||
<view class="hd-cate-item" :class="{ on: currentTab === 'recommend' }" @tap="selectRecommend">推荐</view>
|
||
<view class="hd-cate-item" v-for="(item, index) in lifeCircleList" :key="index"
|
||
:class="{ on: currentTab === item.title }" @tap="selectCircle(item)">
|
||
{{ item.title }}
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 好店瀑布(两列,图片按原始宽高比制造瀑布效果) -->
|
||
<view class="wf">
|
||
<view class="wf-col">
|
||
<view class="wf-card" v-for="item in leftCol" :key="item.id" @click="Info(item)">
|
||
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||
<view class="wf-meta">
|
||
<view class="wf-name">{{ item.merchant_name }}</view>
|
||
<view class="wf-sub">
|
||
<view class="wf-stars">
|
||
<image v-for="i in 5" :key="i" :src="i < item.rating
|
||
? 'https://static.hshuishang.com/property-img-file/local_start1.png'
|
||
: 'https://static.hshuishang.com/property-img-file/local_start2.png'" mode="aspectFill"></image>
|
||
</view>
|
||
<text class="wf-dist">{{ item.distances }}</text>
|
||
</view>
|
||
<view class="wf-tags">
|
||
<text class="wf-tag" v-if="item.refund_property_fee_ratio">买单返岁银</text>
|
||
<text class="wf-tag" v-if="item.refund_user_points_ratio">买单返积分</text>
|
||
</view>
|
||
<view class="wf-addr">{{ item.comAddress }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="wf-col">
|
||
<view class="wf-card" v-for="item in rightCol" :key="item.id" @click="Info(item)">
|
||
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||
<view class="wf-meta">
|
||
<view class="wf-name">{{ item.merchant_name }}</view>
|
||
<view class="wf-sub">
|
||
<view class="wf-stars">
|
||
<image v-for="i in 5" :key="i" :src="i < item.rating
|
||
? 'https://static.hshuishang.com/property-img-file/local_start1.png'
|
||
: 'https://static.hshuishang.com/property-img-file/local_start2.png'" mode="aspectFill"></image>
|
||
</view>
|
||
<text class="wf-dist">{{ item.distances }}</text>
|
||
</view>
|
||
<view class="wf-tags">
|
||
<text class="wf-tag" v-if="item.refund_property_fee_ratio">买单返岁银</text>
|
||
<text class="wf-tag" v-if="item.refund_user_points_ratio">买单返积分</text>
|
||
</view>
|
||
<view class="wf-addr">{{ item.comAddress }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="wf-more" v-if="merchantHasMore">上拉加载更多</view>
|
||
<view class="wf-more" v-else-if="leftCol.length || rightCol.length">没有更多了</view>
|
||
|
||
<nav-footer :current="0" />
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
NavgateTo,
|
||
calculateDistance,
|
||
} from '../../utils/index';
|
||
|
||
import {
|
||
util,
|
||
picUrl,
|
||
request,
|
||
menuButtonInfo
|
||
} from '../../utils/index.js';
|
||
import {
|
||
apiArr
|
||
} from '../../api/community.js';
|
||
import {
|
||
apiArr as apiArr2
|
||
} from '../../api/v2Home.js'
|
||
|
||
import nav from '../../components/nav/nav';
|
||
|
||
// 首页广告位常量(对应后台 home_banner_region.ad_position),与旧首页保持一致
|
||
const AD_POS = {
|
||
TOP_BANNER: 1, // 顶部轮播
|
||
CARD_LEFT: 2, // 广告栏左(大图)
|
||
CARD_RIGHT_TOP: 3, // 广告栏右上
|
||
CARD_RIGHT_BOTTOM: 4, // 广告栏右下
|
||
MID_LEFT: 5, // 中部广告位左
|
||
MID_CENTER_TOP: 6, // 生活圈:中部中上
|
||
MID_CENTER: 7, // 生活圈:中部中
|
||
MID_CENTER_BOTTOM: 8, // 生活圈:中部中下
|
||
MID_RIGHT_TOP: 9, // 生活圈:中部右上
|
||
MID_RIGHT_CENTER: 10, // 生活圈:中部右中
|
||
MID_RIGHT_BOTTOM: 11, // 生活圈:中部右下
|
||
POPUP: 12,
|
||
BOTTOM_RECOMMEND: 13,
|
||
};
|
||
|
||
// 瀑布单列图片宽度(rpx):页面左右各 20rpx 内边距 + 两列间距 20rpx → (750-60)/2 = 345
|
||
const WF_COL_W = 345;
|
||
// 卡片图片下方文字区的估算高度(rpx),仅用于两列高度均衡,不影响真实布局
|
||
const WF_META_H = 150;
|
||
|
||
export default {
|
||
components: { nav },
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
top: 0,
|
||
localHeight: 44,
|
||
cateTop: 0, // 分类条吸顶位置 = 页头(.hd)高度(px)
|
||
|
||
currentCity: '', // 定位信息
|
||
qqmap_key: '',
|
||
|
||
keyword: '', // 搜索关键字(本轮仅占位)
|
||
hotWord: [], // 搜索热词(取第一条做 placeholder)
|
||
|
||
currentIdx: 0,
|
||
bannerList: [], // 轮播图(位1)
|
||
tabList: [], // 金刚区
|
||
rowNum: 0,
|
||
colNum: 0,
|
||
|
||
// 广告栏(位2 / 位3 / 位4)
|
||
serverLeft: '',
|
||
serverLeftList: [],
|
||
serverRightList: [],
|
||
|
||
// 分类条:生活圈(位6-11)
|
||
lifeCircleList: [],
|
||
currentTab: 'recommend', // 当前分类条选中项:'recommend' 或某生活圈标题
|
||
|
||
// 分类(驱动好店列表)
|
||
categoryList: [],
|
||
currentCategoryId: null,
|
||
|
||
// 好店瀑布
|
||
page_num: 1,
|
||
page_size: 10,
|
||
leftCol: [],
|
||
rightCol: [],
|
||
colHeights: [0, 0],
|
||
merchantHasMore: false,
|
||
loadingMore: false,
|
||
|
||
_bannerGroups: null,
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
searchPlaceholder() {
|
||
return this.hotWord && this.hotWord.length ? this.hotWord[0] : '搜索好店 / 商家 / 优惠'
|
||
},
|
||
// 仅「推荐」页展示轮播/金刚区/广告栏等;选中具体生活圈时只留瀑布列表
|
||
isRecommend() {
|
||
return this.currentTab === 'recommend'
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
/* ---------- 顶部/通用 ---------- */
|
||
map() {
|
||
uni.navigateTo({ url: '/pages/shopcity/shopcity' });
|
||
},
|
||
|
||
// 搜索:本轮仅占位。后台好店搜索接口接入后,这里改为按 keyword 过滤 getMechantList。
|
||
handleSearch() {
|
||
const kw = (this.keyword || '').trim()
|
||
// TODO: 接后台好店关键字搜索接口(给 getMerchantList 增加 keyword 参数)
|
||
uni.showToast({
|
||
title: kw ? `搜索「${kw}」功能即将开放` : '请输入关键字',
|
||
icon: 'none'
|
||
})
|
||
},
|
||
|
||
swipers(e) {
|
||
this.currentIdx = e.detail.current
|
||
},
|
||
|
||
NotOpen() {
|
||
uni.showModal({ title: '提示', content: '此功能暂未开通!', showCancel: false, complete: () => { } })
|
||
},
|
||
|
||
// 统一广告点击:按后台 promotion_type 分发(与旧首页一致)
|
||
handleAdClick(item) {
|
||
if (!item) return
|
||
switch (item.promotion_type) {
|
||
case 3:
|
||
if (!item.link_url) return this.NotOpen()
|
||
if (item.link_url === '/packages/shopEnter/index/index') return this.headershopEnterClick()
|
||
return NavgateTo(item.link_url, { isLogin: false })
|
||
case 4:
|
||
if (!item.appid) return this.NotOpen()
|
||
return uni.navigateToMiniProgram({ appId: item.appid, path: item.link_url || '', envVersion: 'release' })
|
||
case 5:
|
||
return NavgateTo('/packages/advertising/index/index?id=' + item.id, { isLogin: false })
|
||
case 1:
|
||
default:
|
||
return this.NotOpen()
|
||
}
|
||
},
|
||
|
||
// 金刚区点击(与旧首页 headerServerClick 一致)
|
||
headerServerClick(e) {
|
||
if (!e.link_url) return this.NotOpen()
|
||
if (e.link_url === '/packages/shopEnter/index/index') return this.headershopEnterClick()
|
||
// #ifdef APP-PLUS
|
||
uni.navigateTo({ url: '/pages/webview/webview?url=' + encodeURIComponent(e.link_url) });
|
||
// #endif
|
||
// #ifdef H5
|
||
window.open(e.link_url, '_blank');
|
||
// #endif
|
||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU
|
||
if (e.appid) {
|
||
uni.navigateToMiniProgram({ appId: e.appid, path: e.link_url, extraData: { data1: 'test' } })
|
||
} else {
|
||
NavgateTo(e.link_url, { isLogin: false })
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
// 「推荐」对应的商品分类 id:暂取美食圈(无则取第一个生活圈,再无则第一个分类)
|
||
recommendCateId() {
|
||
const target = this.lifeCircleList.find(c => c.title === '美食圈') || this.lifeCircleList[0]
|
||
if (target) {
|
||
const cate = this.categoryList.find(c => c.cate_name === target.title)
|
||
if (cate) return cate.id
|
||
}
|
||
return this.categoryList.length ? this.categoryList[0].id : null
|
||
},
|
||
|
||
// 点击「推荐」:暂时显示美食圈内容
|
||
async selectRecommend() {
|
||
this.currentTab = 'recommend'
|
||
const id = this.recommendCateId()
|
||
if (id) await this.switchCategory(id)
|
||
},
|
||
|
||
// 点击某生活圈:只显示该圈对应分类的好店瀑布
|
||
async selectCircle(item) {
|
||
if (item.title == '更多') {
|
||
return NavgateTo('/packages/localLife/index/index', { isLogin: false })
|
||
}
|
||
const cate = this.categoryList.find(c => c.cate_name === item.title)
|
||
if (cate && cate.id) {
|
||
this.currentTab = item.title
|
||
await this.switchCategory(cate.id)
|
||
} else {
|
||
uni.showToast({ title: '暂无「' + item.title + '」相关好店', icon: 'none' })
|
||
}
|
||
},
|
||
|
||
async headershopEnterClick() {
|
||
if (!uni.getStorageSync('userId')) {
|
||
return NavgateTo('/packages/shopEnter/choose/index', { isLogin: false })
|
||
}
|
||
const res = await request(apiArr2.statusQuery, 'POST', {}, { silent: false })
|
||
if (res.status) {
|
||
NavgateTo('/packages/shopEnter/auditStatus/index?itemObj=' + JSON.stringify(res))
|
||
} else {
|
||
NavgateTo('/packages/shopEnter/choose/index')
|
||
}
|
||
},
|
||
|
||
/* ---------- 定位 + 首屏取数 ---------- */
|
||
init() {
|
||
uni.showLoading({ title: '加载中', mask: true })
|
||
this.qqmap_key = '55NBZ-MUQYW-EAJRL-YIWPA-ZXCR6-4NBPP'
|
||
this.getUserLocation()
|
||
},
|
||
|
||
getUserLocation() {
|
||
const that = this
|
||
const { qqmap_key } = this
|
||
const userlocat = uni.getStorageSync('location')
|
||
uni.getLocation({
|
||
type: 'wgs84',
|
||
success: (res) => {
|
||
const latitude = userlocat ? userlocat.lat : res.latitude
|
||
const longitude = userlocat ? userlocat.lng : res.longitude
|
||
uni.request({
|
||
url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${qqmap_key}&get_poi=0`,
|
||
success: async (r) => {
|
||
const { data: { result: { address_component, ad_info } } } = r
|
||
const preciseLocation = {
|
||
cityName: address_component.city,
|
||
region: address_component.province + address_component.city + address_component.district,
|
||
district: address_component.district + `${address_component.street_number ? address_component.street_number : address_component.street}`,
|
||
address: address_component.city + address_component.district + address_component.street_number,
|
||
lat: latitude,
|
||
lng: longitude,
|
||
}
|
||
that.currentCity = preciseLocation
|
||
uni.setStorageSync('location', preciseLocation)
|
||
uni.setStorageSync('ad_code', ad_info.adcode)
|
||
|
||
// 一次拉取各广告位(1-11),后续各 getHomeXxx 读缓存
|
||
await this.prefetchBannerGroups()
|
||
const [bannerList, serverLeft, serverRightList, lifeCircleList, tabList] = await Promise.all([
|
||
this.getHomeBanner(),
|
||
this.getServerLeft(),
|
||
this.getServerRight(),
|
||
this.getLifeCircle(), // 生活圈 6-11 → 分类条
|
||
this.getButtonNum(), // 金刚区
|
||
])
|
||
this.bannerList = bannerList
|
||
this.serverLeft = serverLeft
|
||
this.serverRightList = serverRightList
|
||
this.lifeCircleList = lifeCircleList
|
||
this.tabList = tabList
|
||
|
||
// 先渲染首屏(轮播/金刚/广告/分类条),好店瀑布再异步加载填充,
|
||
// 避免整页等待瀑布——与原主页一致(原页 loading=true 后才单独加载好店列表)。
|
||
this.loading = true
|
||
uni.hideLoading()
|
||
this.$nextTick(() => setTimeout(() => this.measureLayout(), 60)) // 量测吸顶高度 + 对齐金刚区上下间距
|
||
this.getCateList().catch(e => console.warn('好店分类/瀑布加载失败:', e)) // 异步:拉分类 + 加载首个分类的好店瀑布
|
||
},
|
||
fail: (err) => {
|
||
console.log('地图API解析地址失败:', err)
|
||
uni.showToast({ title: '定位失败,请检查网络', icon: 'none' })
|
||
uni.hideLoading()
|
||
}
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
console.log('获取位置信息失败:', err)
|
||
if (err.errMsg && err.errMsg.indexOf('auth deny') >= 0) {
|
||
uni.showModal({
|
||
title: '提示', content: '需要您授权位置信息才能使用此功能', confirmText: '去授权',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// #ifdef MP-WEIXIN
|
||
wx.openSetting();
|
||
// #endif
|
||
// #ifdef APP-PLUS
|
||
uni.openSetting();
|
||
// #endif
|
||
}
|
||
uni.hideLoading()
|
||
}
|
||
})
|
||
} else {
|
||
uni.showToast({ title: '定位失败,请重试', icon: 'none' })
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
/* ---------- 广告位取数(沿用旧首页逻辑) ---------- */
|
||
async prefetchBannerGroups(positions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) {
|
||
try {
|
||
const res = await request(apiArr2.getHomeBannerMulti, 'POST', {
|
||
ad_code: Number(uni.getStorageSync('ad_code')),
|
||
ad_positions: positions,
|
||
longitude: uni.getStorageSync('location').lng,
|
||
latitude: uni.getStorageSync('location').lat,
|
||
page_size: 50
|
||
}, { silent: false })
|
||
this._bannerGroups = (res && res.groups) ? res.groups : {}
|
||
} catch (e) {
|
||
console.warn('批量拉取首页广告失败,回退逐个请求:', e)
|
||
this._bannerGroups = null
|
||
}
|
||
},
|
||
|
||
async getBannerRows(position) {
|
||
if (this._bannerGroups) {
|
||
return this._bannerGroups[String(position)] || []
|
||
}
|
||
const res = await request(apiArr2.getHomeBanner, 'POST', {
|
||
ad_code: uni.getStorageSync('ad_code'),
|
||
ad_position: position,
|
||
longitude: uni.getStorageSync('location').lng,
|
||
latitude: uni.getStorageSync('location').lat,
|
||
page_num: 1,
|
||
page_size: 50
|
||
}, { silent: false })
|
||
return (res && res.rows) ? res.rows : []
|
||
},
|
||
|
||
withPicUrl(list) {
|
||
list.forEach(item => { item.pic_src = picUrl + item.pic_src })
|
||
return list
|
||
},
|
||
|
||
filterShowList(list, type) {
|
||
if (list && list.length) return list.filter(item => item.show_status == type)
|
||
return []
|
||
},
|
||
|
||
async getBannerAt(position, { placeholder = false } = {}) {
|
||
const rows = await this.getBannerRows(position)
|
||
const [first] = this.withPicUrl(this.filterShowList(rows, 1))
|
||
if (first) return first
|
||
return placeholder ? { ad_position: position, pic_src: '' } : null
|
||
},
|
||
|
||
async getBannersAt(positions, opts) {
|
||
return Promise.all(positions.map(p => this.getBannerAt(p, opts)))
|
||
},
|
||
|
||
async getHomeBanner() {
|
||
const rows = await this.getBannerRows(AD_POS.TOP_BANNER)
|
||
return this.withPicUrl(this.filterShowList(rows, 1))
|
||
},
|
||
|
||
// 广告栏固定三格:左大(位2) + 右上(位3) + 右下(位4)。
|
||
// 某位无「已发布且在投放期」广告时用占位对象补齐,保证版式始终是设计稿的三格。
|
||
async getServerLeft() {
|
||
const item = await this.getBannerAt(AD_POS.CARD_LEFT, { placeholder: true })
|
||
this.serverLeftList = [item]
|
||
return item ? item.pic_src : ''
|
||
},
|
||
|
||
async getServerRight() {
|
||
// placeholder:true → 始终返回两格(空位为占位对象,pic_src 为空串)
|
||
return this.getBannersAt([AD_POS.CARD_RIGHT_TOP, AD_POS.CARD_RIGHT_BOTTOM], { placeholder: true })
|
||
},
|
||
|
||
// 是否为真实可点广告(有图);占位格不可点
|
||
isRealAd(item) {
|
||
return !!(item && item.pic_src)
|
||
},
|
||
|
||
// 生活圈:中部 6-11,取各位第一条已发布,作为顶部分类条
|
||
async getLifeCircle() {
|
||
const list = await this.getBannersAt([
|
||
AD_POS.MID_CENTER_TOP, AD_POS.MID_CENTER, AD_POS.MID_CENTER_BOTTOM,
|
||
AD_POS.MID_RIGHT_TOP, AD_POS.MID_RIGHT_CENTER, AD_POS.MID_RIGHT_BOTTOM,
|
||
])
|
||
return list.filter(Boolean).filter(item => item.title)
|
||
},
|
||
|
||
/* ---------- 金刚区 ---------- */
|
||
async getButtonNum() {
|
||
const res = await request(apiArr2.getButtonNum, 'POST', {}, { slice: false })
|
||
this.rowNum = res.nav_row_num
|
||
this.colNum = res.nav_row_total
|
||
return await this.getHomeButton()
|
||
},
|
||
|
||
async getHomeButton() {
|
||
const res = await request(apiArr2.getHomeButton, 'POST', {
|
||
ad_code: Number(uni.getStorageSync('ad_code')),
|
||
page_num: 1,
|
||
page_size: 50
|
||
}, { slice: false })
|
||
const filterRes = this.filterShowList(res?.rows, 1)
|
||
const newList = filterRes.slice(0, this.rowNum * this.colNum)
|
||
if (newList.length) {
|
||
newList.forEach(item => { item.icon_src = picUrl + item.icon_src })
|
||
return newList
|
||
}
|
||
return []
|
||
},
|
||
|
||
/* ---------- 分类 + 好店瀑布 ---------- */
|
||
async getCateList() {
|
||
const res = await request(apiArr2.getCateList, 'POST', { isShop: 1 }, { slice: false })
|
||
if (res.rows && res.rows.length) {
|
||
this.categoryList = res.rows
|
||
// 默认选中「推荐」,内容暂取美食圈
|
||
this.currentTab = 'recommend'
|
||
this.currentCategoryId = this.recommendCateId() || res.rows[0].id
|
||
await this.getMechantList(true)
|
||
} else {
|
||
this.categoryList = []
|
||
}
|
||
},
|
||
|
||
async switchCategory(id) {
|
||
if (this.currentCategoryId === id) return
|
||
this.currentCategoryId = id
|
||
await this.getMechantList(true)
|
||
// 不再强制滚动:分类条常驻吸顶,切换时保持用户当前位置,避免突兀的自动滚动
|
||
},
|
||
|
||
// reset=true 表示重新加载(换分类/下拉刷新);否则为触底追加
|
||
async getMechantList(reset = false) {
|
||
if (this.loadingMore) return
|
||
this.loadingMore = true
|
||
if (reset) {
|
||
this.page_num = 1
|
||
this.leftCol = []
|
||
this.rightCol = []
|
||
this.colHeights = [0, 0]
|
||
}
|
||
try {
|
||
const res = await request(apiArr2.getMerchantList, 'POST', {
|
||
merchant_cate_id: this.currentCategoryId,
|
||
page_num: this.page_num,
|
||
page_size: this.page_size,
|
||
})
|
||
const latitude = uni.getStorageSync('location').lat
|
||
const longitude = uni.getStorageSync('location').lng
|
||
const rows = (res && res.rows) ? res.rows : []
|
||
rows.forEach((item) => {
|
||
item.bigImg = item.album_images?.split(',')
|
||
item.showImg = item.album_images ? picUrl + item.album_images.split(',')[0] : ''
|
||
const distanceInKm = calculateDistance(latitude, longitude, item.latitude, item.longitude)
|
||
item.distances = distanceInKm >= 1
|
||
? `${distanceInKm.toFixed(1)}km`
|
||
: `${(distanceInKm * 1000).toFixed(1)}m`
|
||
item.comAddress = item.ad ? item.ad.ad_name.replace(/,/g, '') + item.address : item.address
|
||
})
|
||
// 就近排序
|
||
rows.sort((a, b) => {
|
||
const va = parseFloat(a.distances) * (a.distances.includes('km') ? 1000 : 1)
|
||
const vb = parseFloat(b.distances) * (b.distances.includes('km') ? 1000 : 1)
|
||
return va - vb
|
||
})
|
||
|
||
this.merchantHasMore = rows.length === this.page_size
|
||
if (this.merchantHasMore) this.page_num++
|
||
|
||
await this.appendMasonry(rows)
|
||
} catch (e) {
|
||
console.warn('好店列表加载失败:', e)
|
||
} finally {
|
||
this.loadingMore = false
|
||
}
|
||
},
|
||
|
||
// 并行量图(并发比串行快很多)→ 按两列累计高度均衡分列,避免一列过长、另一列底部大片留白。
|
||
// 图片仍 mode="widthFix" 自适应显示;此处测量仅用于分列均衡。
|
||
async appendMasonry(rows) {
|
||
const ratios = await Promise.all(rows.map(item => new Promise(resolve => {
|
||
if (!item.showImg) return resolve(0.8)
|
||
uni.getImageInfo({
|
||
src: item.showImg,
|
||
success: info => resolve(info && info.width ? info.height / info.width : 0.8),
|
||
fail: () => resolve(0.8)
|
||
})
|
||
})))
|
||
rows.forEach((item, i) => {
|
||
item.imgH = Math.round(WF_COL_W * ratios[i]) // 显式高度,即时到位不回流
|
||
const target = this.colHeights[0] <= this.colHeights[1] ? 0 : 1
|
||
if (target === 0) this.leftCol.push(item)
|
||
else this.rightCol.push(item)
|
||
this.colHeights[target] += item.imgH + WF_META_H
|
||
})
|
||
},
|
||
|
||
// 量测页头真实高度,供分类条吸顶定位
|
||
measureLayout() {
|
||
uni.createSelectorQuery().in(this)
|
||
.select('.hd').boundingClientRect(rect => {
|
||
if (rect && rect.height) this.cateTop = Math.round(rect.height)
|
||
}).exec()
|
||
},
|
||
|
||
Info(e) {
|
||
uni.setStorageSync('merchantInfo', e)
|
||
NavgateTo('/packages/localLife/detail/index', { isLogin: false })
|
||
},
|
||
|
||
async getSearchVal() {
|
||
try {
|
||
const res = await request(apiArr.hotWord, 'POST', {}, { slice: false })
|
||
this.hotWord = res.search_hot_word ? res.search_hot_word.split(/[,,]/) : []
|
||
} catch (e) { this.hotWord = [] }
|
||
},
|
||
},
|
||
|
||
onLoad() {
|
||
const menu = menuButtonInfo()
|
||
this.top = menu.top
|
||
this.localHeight = menu.height
|
||
// 分类条吸顶位置 = 页头高度:状态栏 top(px) + 页头内容(定位64+搜索8+72+下内边距20=164rpx)换算 px
|
||
const sys = uni.getSystemInfoSync()
|
||
this.cateTop = Math.round(this.top + 164 * sys.windowWidth / 750)
|
||
this.init()
|
||
this.getSearchVal()
|
||
},
|
||
onReady() {
|
||
// 用真实渲染高度校正吸顶位置(估算可能有偏差)
|
||
this.$nextTick(() => {
|
||
uni.createSelectorQuery().in(this).select('.hd').boundingClientRect(rect => {
|
||
if (rect && rect.height) this.cateTop = Math.round(rect.height)
|
||
}).exec()
|
||
})
|
||
},
|
||
|
||
onShow() {
|
||
const storageLocation = uni.getStorageSync('location')
|
||
if (storageLocation && this.loading) {
|
||
// 仅当定位城市/区域「真正变化」时才重新初始化。
|
||
// 注意:不能拿 currentCity.address 和 storageLocation.district 比(两者是不同字段,
|
||
// 几乎恒为不等),否则每次返回首页都会重载 → 回到顶部、丢失当前分类,体验很差。
|
||
const changed = !this.currentCity ||
|
||
this.currentCity.cityName !== storageLocation.cityName ||
|
||
this.currentCity.district !== storageLocation.district
|
||
if (changed) {
|
||
this.currentCity = storageLocation
|
||
this.init()
|
||
}
|
||
}
|
||
},
|
||
|
||
onPullDownRefresh() {
|
||
this.init()
|
||
uni.stopPullDownRefresh()
|
||
},
|
||
|
||
async onReachBottom() {
|
||
if (this.merchantHasMore && !this.loadingMore) {
|
||
await this.getMechantList(false)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
@import url("./index2.css");
|
||
</style>
|