Files
hcb-h5/src/pages/Home.vue
T
2025-12-09 16:48:38 +08:00

652 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<div v-if="is_first_load" class="loading relative">
<PageInitLoading/>
</div>
<transition name="fade">
<div v-if="!is_first_load" class="home-page relative" style="min-height: 100vh;">
<div class="home-bg absolute left-0 top-0 right-0 bottom-0"></div>
<div :style="'padding-top:'+(370/7.5)+'vw;'"></div>
<div v-if="cityInfo&&cityInfo.cityName" class="absolute right-0 top-0 mr30 mt50 z-index-1">
<div @click.stop="handleShowCityList" class="bg-fff font-24 pl15 pr15 pt10 pb10 ulib-r10">{{
cityInfo.cityName
}}
<van-icon name="play"/>
</div>
</div>
<div class="relative ml30 mr30">
<div class="home-options bg-fff ulib-r20 inner30">
<div class="home-step-bg"></div>
<div class="home-search ulib-r750 overflowhidden inner5 mt30"
style="border:1px solid var(--van-button-danger-background)">
<div class="fn-flex fn-flex-center">
<div class="fn-flex-item pl20">
<van-field v-model="keywords" label="" placeholder="请输入搜索关键词" style="padding:0"/>
</div>
<div>
<van-button style="width:16vw;" type="danger" round size="small" @click="onSearch">搜索</van-button>
</div>
</div>
</div>
<div class="home-companys mt10 fn-flex" v-if="cityBrandList.length>0">
<div class="fn-flex-item relative">
<!--
<div class="absolute left-0 right-0 top-0 scroll-x" style="white-space: nowrap;">
<template v-for="(item,index) in cityBrandList" :key="index">
<div v-if="index<6" class="text-center inline-block mr20" @click.stop="chooseBrandItem(item)">
<div class="imgsize-52X52" style="margin-left:auto;margin-right:auto;"><van-image class="imgsize-52X52" fit="cover" :src="item.logo" /></div>
<p class="font-24 mt10">{{item.name}}</p>
</div>
</template>
</div>
-->
<div class="left-0 right-0 top-0 scroll-x">
<template v-for="(item,index) in cityBrandList" :key="index">
<div class="text-center inline-block mr20 mt20" @click.stop="chooseBrandItem(item)">
<div class="imgsize-52X52" style="margin-left:auto;margin-right:auto;">
<van-image class="imgsize-52X52" fit="cover" :src="item.logo"/>
</div>
<p class="font-24 mt10">{{ item.name }}</p>
</div>
</template>
<!--
<div class="text-center inline-block mr20" @click="showBrandList= true">
<van-icon name="clock-o" size="25" style="transform: rotate(-135deg);"/>
<p class="font-24 mt10">更多</p>
</div>
-->
</div>
</div>
<!--
<div class="text-center" @click="showBrandList= true">
<van-icon name="clock-o" size="25" style="transform: rotate(-135deg);"/>
<p class="font-24 mt10">更多</p>
</div>
-->
</div>
<SideMenu/>
<div class="home-filter mt15">
<!-- <div v-if></div> -->
<div>
<ul class="fn-flex fn-flex-wrap">
<li
class="ulib-r10 pl10 pr10 pt10 pb10 mt15 font-24 mr5"
v-for="(item,index) in carProductLabel"
:key="index"
@click.stop="bindChangeTagList(index,'carProductLabel')"
:style="{
backgroundColor: index === cur_carProductLabel ? '#ffeded' : '#f9f9f9',
border:'1px solid ' + (index === cur_carProductLabel ? '#ffcaca' : '#f9f9f9')
}">{{ item.name }}
</li>
</ul>
</div>
<div>
<ul class="fn-flex fn-flex-wrap">
<li
class="ulib-r10 pl10 pr10 pt10 pb10 mt15 font-24 mr5"
v-for="(item,index) in priceRange"
:key="index"
@click.stop="bindChangeTagList(index,'priceRange')"
:style="{
backgroundColor: index === cur_priceRange ? '#ffeded' : '#f9f9f9',
border:'1px solid '+(index === cur_priceRange ? '#ffcaca' : '#f9f9f9')
}">{{ item.name }}
</li>
</ul>
</div>
<div>
<ul class="fn-flex fn-flex-wrap">
<li
class="ulib-r10 pl10 pr10 pt10 pb10 mt15 font-24 mr5"
v-for="(item,index) in productLevel"
:key="index"
@click.stop="bindChangeTagList(index,'productLevel')"
:style="{
backgroundColor: index === cur_productLevel ? '#ffeded' : '#f9f9f9',
border:index === cur_productLevel ?'1px solid #ffcaca':'1px solid #f9f9f9'
}">{{ item.name }}
</li>
</ul>
</div>
</div>
<div class="mt10 fn-flex fn-flex-center">
<div class="fn-flex-item">
<van-tag v-if="cur_brand_list&&cur_brand_list.id" class="mr5" closeable size="mini" color="#f84803"
plain @close="bindCloseTag('brand')">{{ cur_brand_list.name }}
</van-tag>
<van-tag v-if="cur_carProductLabel>-1" class="mr5" closeable size="mini" color="#f84803" plain
@close="bindCloseTag('carProductLabel')">{{ carProductLabel[cur_carProductLabel].name }}
</van-tag>
<van-tag v-if="cur_priceRange>-1" class="mr5" closeable size="mini" color="#f84803" plain
@close="bindCloseTag('priceRange')">{{ priceRange[cur_priceRange].name }}
</van-tag>
<van-tag v-if="cur_productLevel>-1" class="mr5" closeable size="mini" color="#f84803" plain
@close="bindCloseTag('productLevel')">{{ productLevel[cur_productLevel].name }}
</van-tag>
</div>
<span
v-if="(cur_brand_list&&cur_brand_list.id)||cur_carProductLabel>-1||cur_priceRange>-1||cur_productLevel>-1"
class="font-24 text-middle fn-flex fn-flex-center color-999" @click.stop="bindCloseTag('all')"><span>清空</span><van-icon
size="20px" name="delete-o"/></span>
</div>
</div>
<div class="home-list mt10" v-if="empty">
<van-empty description="暂无数据"/>
</div>
<div class="home-list mt10" v-else>
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<template v-for="(item, index) in productList" :key="index">
<div class="home-item box-shadow-darkGray inner30 mb40 ulib-r20">
<div class="item" @click.stop="pushLink(item)">
<div class="fn-flex">
<div class="mr20">
<van-image class="imgsize-200X152" radius="10px" :src="item.banner"/>
</div>
<div class="fn-flex-item fn-flex fn-flex-column">
<div class="fn-flex-item">
<div class="fn-flex fn-flex-center justify-between">
<div class="wp50 font-30 line-clamp-1">{{ item.title }}</div>
<div class="tag fn-flex fn-flex-wrap text-right">
<van-tag
v-for="(label, labelIndex) in item.carProductLabel"
:key="labelIndex"
class="ml5"
color="#f6f6f6"
round
text-color="#1a1a1a"
>{{ label }}
</van-tag>
</div>
</div>
<div class="mt15 line-clamp-1 color-333"><p class="font-26" v-if="item.guide_price">
指导价{{ item.guide_price }}</p></div>
<div class="mt15" style="--van-tag-radius:1vw;--van-tag-font-size:3.4vw;">
<van-tag v-if="item.discounts" color="#f84803" plain style="padding-left:0"><i
class="icon-custom icon-redpack mr5"></i>{{ item.discounts }}
</van-tag>
</div>
</div>
</div>
</div>
<div class="fn-flex fn-flex-center justify-between">
<div class="wp65 ">
<div v-if="item.discounts2" style="color:#f00;"
class="text-color-theme font-28 fn-flex fn-flex-center"><i
class="icon-custom icon-money mr5"></i><span class="line-clamp-1">{{
item.discounts2
}}</span></div>
<div v-if="item.discounts3" style="color:#f00;"
class="text-color-theme font-28 fn-flex fn-flex-center mt10"><i
class="icon-custom icon-gift mr5"></i><span class="line-clamp-1">{{
item.discounts3
}}</span></div>
</div>
<div class=""
:style="{'--van-button-default-line-height':(70/7.5)+'vw','--van-button-default-height':(70/7.5)+'vw'}">
<van-button type="danger" class="pl40 pr40">{{ item.btText || '立即领取' }}</van-button>
</div>
</div>
</div>
</div>
</template>
<div v-if="productList.length === 0 && !loading && finished" class="text-center pt30 pb30 color-666">
暂无数据
</div>
</van-list>
</div>
</div>
<van-popup
v-model:show="showBrandList"
position="right"
:style="{ width: '80%', height: '100%' }"
>
<BrandList :is-sticky="false" @brandItem="handleBrandItem"/>
</van-popup>
<van-popup
v-model:show="showCityList"
position="right"
:style="{ width: '80%', height: '100%' }"
>
<CityChooseList @cityItem="handleCityItem"/>
</van-popup>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue'
import { useRouter } from 'vue-router'
import SideMenu from '@/components/SideMenu.vue' // 导入SideMenu组件
import BrandList from '../components/BrandList.vue' // 导入BrandList组件
import CityChooseList from '../components/CityChooseList.vue' // 导入CityChooseList组件
import PageInitLoading from '../components/PageInitLoading.vue'
import WechatUtils, { WechatAuth, WechatLocation, WechatShare } from '@/utils/wechat'
import { useUserStore } from '@/stores/user'
import api from '@/utils/api'
import { useBrandStore } from '@/stores/brand'
import { showToast } from 'vant'
import { trackEvent } from '@/utils/analytics'
// 导入 cookie 工具
import { getCookie, setCookie } from '@/utils/cookie'
import { getUserLocation, getCityInfo, cacheLocationInfo } from '@/utils/location'
const route = useRouter()
const is_first_load = ref(true)
const keywords = ref('') // 声明响应式搜索关键词变量
const brand_list = ref([]) // 声明品牌列表变量
const showBrandList = ref(false) // 控制品牌列表的显示和隐藏
const cur_brand_list = ref({}) // 声明品牌列表变量
const showCityList = ref(false) // 控制城市列表的显示和隐藏
const carProductLabel = ref([]) // 声明响应式搜索关键词变量
const priceRange = ref([]) // 声明响应式搜索关键词变量
const productLevel = ref([]) // 声明响应式搜索关键词变量
const cur_carProductLabel = ref(-1) // 声明响应式搜索关键词变量
const cur_priceRange = ref(-1) // 声明响应式搜索关键词变量
const cur_productLevel = ref(-1) // 声明响应式搜索关键词变量
// 产品列表数据
const productList = ref([])
const loading = ref(false)
const finished = ref(false)
const page = ref(1)
const pageSize = ref(10)
const empty = ref(false)
const cityBrandList = ref([])
// 查询参数
const query_data = reactive({
page: 1,
size: 10,
title: '',
brandId: '',
labelIds: '',
cityId: ''
})
// 城市信息
const cityInfo = ref(null)
onMounted(async () => {
let appid = localStorage.getItem('app_id')
if (appid) {
const wechat = await WechatUtils.init(appid)
// 隐藏分享
await WechatShare.hideAllNonBaseMenuItem()
}
// 初始化完成后获取位置信息
await getCurrentLocation()
await bindInitPage()
})
const bindInitPage = async () => {
// 获取字典列表数据
await getDictionaryList()
await getCarBrands()
await getCityBrands()
try {
empty.value = false
await getProductList()
} catch (error) {
console.log(error)
}
}
/**
* 品牌列表
*/
const getCarBrands = async () => {
const brandStore = useBrandStore()
if (brandStore.list && Object.keys(brandStore.list).length > 0) {
brand_list.value = brandStore.list
return brandStore.list
}
try {
const { data } = await api.get('/auto/car/brand')
if (data) {
brand_list.value = data
brandStore.setList(data)
}
return data
} catch (error) {
console.error('获取品牌数据失败:', error)
return { code: 500, message: '接口请求异常' }
}
}
/**
* 获取字典列表
* @param {string} dictCode - 字典名称,默认为'carProductLabel'
*/
const getDictionaryList = async (dictCode = 'carProductLabel,priceRange,productLevel') => {
try {
const result = await api.get('/auto/config/dictionaryData', { dictCode })
if (result.code === 200 && result.data) {
carProductLabel.value = result.data.carProductLabel
priceRange.value = result.data.priceRange
productLevel.value = result.data.productLevel
// console.log('字典列表:', result.data);
}
} catch (error) {
console.error('获取字典列表失败:', error)
}
}
/**
* 获取当前位置信息
* @returns {Promise<void>}
*/
const getCurrentLocation = async () => {
// 非微信环境从URL获取cityId
const urlParams = new URLSearchParams(window.location.search)
const urlCityId = urlParams.get('cityId')
try {
// 检查是否为微信浏览器
const isWechat = WechatAuth.isWechatBrowser()
if (!isWechat) {
if (urlCityId) {
try {
const cityResult = await api.get('/auto/config/city', { cityId: urlCityId })
if (cityResult.code === 200 && cityResult.data) {
cacheLocationInfo('', cityResult.data)
await handleCityInfo(cityResult.data)
return
}
} catch (cityError) {
console.error('通过cityId获取城市信息失败:', cityError)
}
}
}
// 微信环境或非微信环境未获取到cityId时使用定位逻辑
const location = await getUserLocation()
console.log('当前位置:', location)
if (!(location && location.latitude && location.longitude)) {
return
}
const cityInfoData = await getCityInfo(location)
if (cityInfoData && cityInfoData.cityId) {
cacheLocationInfo(location, cityInfoData)
await handleCityInfo(cityInfoData)
}
} catch (error) {
console.error('获取位置或解析城市失败:', error)
if (urlCityId) {
const cityResult = await api.get('/auto/config/city', { cityId: urlCityId })
if (cityResult.code === 200 && cityResult.data) {
cacheLocationInfo('', cityResult.data)
await handleCityInfo(cityResult.data)
}
}
}
}
/**
* 处理城市信息
* @param {Object} cityInfoData 城市信息数据
*/
const handleCityInfo = async (cityInfoData) => {
cityInfo.value = cityInfoData
console.log('城市信息:', cityInfoData)
const userStore = useUserStore()
userStore.setCity(cityInfoData)
query_data.cityId = cityInfoData.cityId
empty.value = false
await getProductList()
}
/**
* 获取产品列表
*/
// 防重复请求状态
const isRequesting = ref(false)
const getProductList = async () => {
// 防止同时请求和重复请求
if (isRequesting.value || finished.value) {
return
}
try {
isRequesting.value = true
// loading.value = true;
const result = await api.get('/auto/car/product/list', query_data)
if (result.code === 200 && result.data) {
if (query_data.page === 1 && result.data.list.length === 0) {
empty.value = true
finished.value = true
return
}
if (query_data.page === 1) {
productList.value = result.data.list || []
} else {
productList.value = [...productList.value, ...(result.data.list || [])]
}
// 判断是否加载完成
if (!result.data.list || result.data.list.length < query_data.size) {
finished.value = true
return
}
// 更新页码
query_data.page++
}
} catch (error) {
console.error('获取产品列表失败:', error)
} finally {
loading.value = false
isRequesting.value = false
is_first_load.value = false
}
}
// 搜索事件处理
const onSearch = () => {
// 重置查询参数
if (!keywords.value) {
showToast('请输入搜索关键字')
return
}
//友盟统计
trackEvent('search', 'CLK', {
Key_SearchKeyword: keywords.value
})
query_data.page = 1
query_data.title = keywords.value
finished.value = false
empty.value = false
// productList.value = [];
// 重新加载数据
getProductList()
}
// 按钮点击处理
const pushLink = (item) => {
let url = '/item/' + item.id
//友盟统计
// trackEvent('list', 'CLK', {
// Key_ProductId: item.id,
// Key_ProductTitle: item.title,
// Key_Brand: item.brandName,
// Key_Series: item.seriesName
// });
route.push(url)
}
// 加载更多数据
const onLoad = () => {
getProductList()
}
// 切换标签
const bindChangeTagList = (index, type) => {
if (type === 'carProductLabel') {
cur_carProductLabel.value = index
} else if (type === 'priceRange') {
cur_priceRange.value = index
} else if (type === 'productLevel') {
cur_productLevel.value = index
}
checkHasLabelIds()
query_data.page = 1
finished.value = false
empty.value = false
// productList.value = [];
getProductList()
}
const chooseBrandItem = (item) => {
cur_brand_list.value = item
query_data.brandId = item.id
query_data.page = 1
finished.value = false
empty.value = false
getProductList()
}
const handleBrandItem = (item) => {
cur_brand_list.value = item
query_data.brandId = item.id
query_data.page = 1
finished.value = false
showBrandList.value = false
empty.value = false
getProductList()
}
const handleCityItem = (item) => {
const userStore = useUserStore()
query_data.cityId = item.cityId
query_data.page = 1
finished.value = false
empty.value = false
showCityList.value = false
console.log('item', item)
cityInfo.value = {}
cityInfo.value.cityName = item.cityName
cityInfo.value.cityId = item.cityId
userStore.setCity(cityInfo.value)
// localStorage.setItem('cityInfo', JSON.stringify(cityInfo.value));
// 修改为使用 cookie 存储
// setCookie('cityInfo', cityInfo.value, 1)
cacheLocationInfo('', cityInfo.value)
getCityBrands()
getProductList()
}
const handleShowCityList = () => {
showCityList.value = true
}
const bindCloseTag = (type) => {
if (type === 'brand') {
cur_brand_list.value = {}
query_data.brandId = ''
} else if (type === 'carProductLabel') {
cur_carProductLabel.value = -1
checkHasLabelIds()
} else if (type === 'priceRange') {
cur_priceRange.value = -1
checkHasLabelIds()
} else if (type === 'productLevel') {
cur_productLevel.value = -1
checkHasLabelIds()
} else if (type === 'all') {
cur_brand_list.value = {}
query_data.brandId = ''
cur_carProductLabel.value = -1
cur_priceRange.value = -1
cur_productLevel.value = -1
query_data.labelIds = ''
}
query_data.page = 1
finished.value = false
empty.value = false
getProductList()
}
const checkHasLabelIds = () => {
let arr = []
if (cur_carProductLabel.value > -1) {
arr.push(carProductLabel.value[cur_carProductLabel.value].id)
}
if (cur_priceRange.value > -1) {
arr.push(priceRange.value[cur_priceRange.value].id)
}
if (cur_productLevel.value > -1) {
arr.push(productLevel.value[cur_productLevel.value].id)
}
query_data.labelIds = arr.join(',')
}
const removeElement = (arr, value) => {
return arr.filter(function (item) {
return item !== value
})
}
/**
* 城市品牌列表
*/
const getCityBrands = async () => {
try {
let query_params = { cityId: query_data.cityId }
const { data } = await api.get('/auto/brand/cityBrand', query_params)
if (data) {
cityBrandList.value = data.list
}
return data
} catch (error) {
console.error('获取品牌数据失败:', error)
return { code: 500, message: '接口请求异常' }
}
}
</script>
<style lang="scss" scoped>
.home-bg {
width: 100vw;
background-size: 100% auto;
background-position: top center;
background-repeat: no-repeat;
background-image: url('/src/assets/index-bg-1011.png')
}
.home-step-bg {
width: 630px;
height: 30px;
background-size: 100% auto;
background-position: top center;
background-repeat: no-repeat;
background-image: url('/src/assets/index-steps.png')
}
</style>