Files
gterFang/src/views/apartmentDetail.vue
DESKTOP-RQ919RC\Pc 5fd6f68a7c refactor: 移除调试日志并优化代码结构
- 删除多个组件中的console.log调试语句
- 优化axios错误处理中的可选链操作
- 重构wechat-btn组件的事件监听逻辑
- 清理ai.vue中的冗余代码和注释
- 改进页面可见性变化的处理逻辑
2025-08-25 18:53:18 +08:00

2424 lines
100 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>
<page-top-bar></page-top-bar>
<div class="content wid1200">
<div class="header" ref="headerRef">
<div class="top flexflex">
<div class="brand-abstract flexflex">
<div class="flexacenter" style="height: min-content">
<a class="item" href="/">港校租房</a>
<img class="arrow" alt="箭头" src="@/assets/img/publicImage/yellow-arrow.svg" />
<a class="item" href="/apartment">品牌公寓</a>
<img class="arrow" alt="箭头" src="@/assets/img/publicImage/yellow-arrow.svg" />
<a class="item" :href="`/apartment?companyid=${company.id}`">{{ company.title }}</a>
</div>
</div>
<img class="arc-bj" alt="视觉上角的图片" src="@/assets/img/publicImage/angle.png" />
</div>
<div class="header-content flexflex">
<div class="header-left">
<!-- <image-watch style="z-index: 10000;" arrow="never" :index="imageIndex" :show="imageShow" :close="cloaseImageShow" :list="imageList"></image-watch> -->
<image-watch arrow="never" :index="imageIndex" v-if="imageShow" :close="cloaseImageShow"
:list="imageList"></image-watch>
<div class="slideshow">
<el-carousel :autoplay="false" arrow="never" indicator-position="none" ref="remarkCaruselUp"
@change="carouselChange">
<el-carousel-item class="flexcenter" v-for="(item, index) in allCarouselsData" :key="index">
<img v-if="index >= carouselIndex - 1 && index <= carouselIndex + 1" class="img"
:src="item['image'] || item['imageurl']" alt="公寓图片"
@click="cloaseImageShow(allCarouselsData, carouselIndex, 'carousel')" />
</el-carousel-item>
</el-carousel>
<div class="indicate-type flexacenter" v-if="indicateTypeState()">
<div class="indicate-item"
:class="{ pitch: allCarouselsData[carouselIndex]['type'] == 'lives' }"
v-if="info['lives'] && info['lives'].length != 0" @click="slideshowType('lives')">直播
</div>
<div class="indicate-item"
:class="{ pitch: allCarouselsData[carouselIndex]['type'] == 'videos' }"
v-if="info['videos'] && info['videos'].length != 0" @click="slideshowType('videos')">视频
</div>
<div class="indicate-item"
:class="{ pitch: allCarouselsData[carouselIndex]['type'] == 'attachment' }"
v-if="info['attachment'] && info['attachment'].length != 0"
@click="slideshowType('attachment')">图片</div>
</div>
<div class="indicate" v-if="allCarouselsData.length != 0">{{ carouselIndex -
carouselsconfig[allCarouselsData[carouselIndex]["type"]]["index"] + 1 }}/{{
carouselsconfig[allCarouselsData[carouselIndex]["type"]]["amount"] }}</div>
</div>
<div class="slideshow-across flexflex">
<div class="slideshow-btn left flexcenter" @click="handleslideshow('left')">
<img v-if="carouselIndex == 0" class="arrow" alt="不可点击的左箭头切换"
src="@/assets/img/publicImage/gray-arrow.svg" />
<img v-else class="arrow rotate180" alt="可点击的左箭头切换"
src="@/assets/img/publicImage/black-arrow.svg" />
</div>
<div ref="slideshowList" class="slideshow-list box no-scrollbar flex1 flexacenter">
<!-- <div class="item" :class="({ pitch: index == carouselIndex }, `item${index}`)" v-for="(item, index) in allCarouselsData" @click="slideshowItem(index)" :key="index"> -->
<div class="item" :class="getClass(index)" v-for="(item, index) in allCarouselsData"
@click="slideshowItem(index)" :key="index">
<img class="img" alt="公寓图片缩略图" v-lazy="item['thumbnail'] || item['imageurl']" />
<img class="video-icon" v-if="item['type'] != 'attachment'"
src="@/assets/img/publicImage/video-icon.svg" />
</div>
</div>
<div class="slideshow-btn flexcenter" @click="handleslideshow('right')">
<img v-if="carouselIndex == allCarouselsData.length - 1" class="arrow rotate180"
alt="不可点击的右箭头切换" src="@/assets/img/publicImage/gray-arrow.svg" />
<img v-else class="arrow" alt="可点击的右箭头切换" src="@/assets/img/publicImage/black-arrow.svg" />
</div>
</div>
</div>
<div class="header-right flex1">
<img class="header-bj" alt="头部的地图背景"
src="@/assets/img/apartmentDetail/apartmentDetail-header-bj.jpg" />
<!-- <img class="header-shade" src="@/assets/img/apartmentDetail/apartmentDetail-header-shade.svg"> -->
<!-- <div class="tab-box flexflex" v-if="info['tags'] && info['tags'].length != 0">
<div class="tab-item wordbreak flexcenter" v-for="(item, index) in info['tags']" :key="index">{{ item }}</div>
</div> -->
<div class="apartment-name wordbreak" v-if="info['title']">{{ info["title"] }}</div>
<div class="label-list flexacenter">
<div class="label-item flexacenter blue" v-if="info.fieldtrips == 1">
<img class="safety-icon" src="@/assets/img/publicImage/safety-icon.png" />
实地考察
</div>
<div class="label-item flexacenter violet">0服务费</div>
<div class="label-item red" v-for="(item, index) in info.hottags" :key="index">{{ item }}</div>
<div class="label-item" v-for="(item, index) in info.tags" :key="index">{{ item }}</div>
</div>
<div class="introduce">
<div class="introduce-head flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/yellow-diamond.png" />
<div class="name">{{ company.title }}</div>
<div class="full-name flex1">{{ info.propaganda }}</div>
<div class="more flexacenter" v-if="withsameapartments" @click="handleClickNav('eleseEle')">
同品牌
<img class="icon" src="@/assets/img/publicImage/black-arrow.svg" />
</div>
</div>
<div class="synopsis wordbreak">{{ info["introduction"] }}</div>
</div>
<div class="place flexflex" v-if="info['address']">
<div class="place-head flex1 flexacenter">
<img class="icon" alt="地图-图标" src="@/assets/img/publicImage/location-icon.png" />
<div class="text flex1 ellipsis">{{ info["address"] }}</div>
<div class="more flexacenter" @click="handleClickNav('addressEle')">
地图
<img class="icon" alt="地图-箭头" src="@/assets/img/publicImage/black-arrow.svg" />
</div>
</div>
<div class="figure flexacenter" v-if="distancePitch?.alias">
距离
<div class="school">{{ distancePitch.alias }}</div>
<template v-if="distancePitch.distance > 0">{{ distancePitch.distance }}km</template>
<div class="btn" @click="openSelectSchool">[切换院校]</div>
</div>
<div class="vehicle flexflex" v-if="distancePitch.distanceArr?.length > 0">
<div class="item flexcenter" v-for="(item, index) in distancePitch.distanceArr"
:key="index">{{ item.text +
item.duration }}</div>
<!-- <div class="item flexcenter" v-if="distancePitch?.walking_duration > 0">
步行{{ calculateDuration(distancePitch.walking_duration) }}
</div>
<div class="item flexcenter" v-if="distancePitch?.driving_duration > 0">
地铁{{ calculateDuration(distancePitch.driving_duration) }}
</div>
<div class="item flexcenter" v-if="distancePitch?.transit_duration > 0">
巴士{{ calculateDuration(distancePitch.transit_duration) }}
</div> -->
</div>
</div>
<!-- <div class="else flexacenter" v-if="withsameapartments">
<div class="left flexacenter">
<img class="icon" alt="同品牌其他公寓-图标" src="@/assets/img/apartmentDetail/yellow-diamond.png" />
同品牌其他公寓
</div>
<div class="right flexacenter" @click="handleClickNav('eleseEle')">
<div class="quantity flexcenter" :aria-label="withsameapartments + '个同品牌其他公寓'">{{ withsameapartments }}</div>
<img class="icon" alt="同品牌其他公寓-箭头" src="@/assets/img/publicImage/black-arrow.svg" />
</div>
</div> -->
</div>
</div>
</div>
<div class="operate-box-bj flexcenter" v-if="isOperateShow">
<div class="operate-box flexacenter" aria-label="详情的导航栏">
<div class="nav-box flexacenter">
<div class="nav-item flexcenter" :class="{ pitch: navTab == item.value }"
:aria-label="`${item['name']}-按钮`" v-for="(item, index) in navList" :key="index"
@click="handleClickNav(item.value)">{{ item["value"] ==
"roomEle" ? `${item["name"]} ${roomList.length}` : item["name"] }}</div>
</div>
<div class="btn-box flexacenter">
<div class="btn-item transmit-btn flexcenter" @click="handleCollect">
<img v-if="info.iscollect == 1" alt="收藏图标" class="transmit-icon"
src="@/assets/img/detail/collectT.png" />
<img v-else alt="收藏图标" class="transmit-icon" src="@/assets/img/detail/collect.png" />
收藏
</div>
<!-- <div class="btn-item transmit-btn flexcenter" @click="handleTransmit">
<img alt="转发图标" class="transmit-icon" src="@/assets/img/publicImage/transmit-icon.png" />
转发
<transmit-btn :qrcode="qrcode" :title="info['title']" type="apartment"></transmit-btn>
</div> -->
<div class="btn-item consult-btn flexcenter" @click="modificationContact">咨询</div>
</div>
</div>
</div>
<div class="details-box flexflex" ref="detailsBox">
<div class="flex1">
<div class="details-left flex1" :class="{ fixed: isFixed }" :style="{ bottom: FixedBottom + 'px' }"
ref="detailsLeft">
<div class="special-offer" v-if="info.promotionalactivities" ref="specialEle">
<img class="special-bj" src="@/assets/img/publicImage/special-bj.svg" />
<img class="head" src="@/assets/img/publicImage/special-title.png" />
<img class="gift" src="@/assets/img/publicImage/special-gift.png" />
<img class="star1" src="@/assets/img/publicImage/special-star-1.png" />
<img class="star2" src="@/assets/img/publicImage/special-star-2.png" />
<img class="star3" src="@/assets/img/publicImage/special-star-3.png" />
<img class="fireworks" src="@/assets/img/publicImage/special-fireworks.png" />
<div class="board">
<div class="gray">
<p class="text" user-select>{{ info.promotionalactivities }}</p>
</div>
</div>
<img class="bottom-white" src="@/assets/img/publicImage/special-bottom-white.svg" />
<img class="bottom-orange" src="@/assets/img/publicImage/special-bottom-orange.svg" />
</div>
<div class="remark" ref="inspectEle" v-if="spotSum > 0" @click="openInspectPop">
<div class="head flexacenter">
<div class="text">寄托实地考察</div>
<div class="more flexacenter">
{{ spotSum }}
<img class="icon" src="@/assets/img/publicImage/arrow-black-down.svg" />
</div>
</div>
<div class="inspect flexacenter">
<div class="left flex1">
<div class="info flexacenter">
<img class="avatar" :src="spotObj.avatar" />
<div class="username">{{ spotObj.nickname }}</div>
<img class="label" :src="spotObj.groupimage" />
</div>
<div class="explain two-line-text">{{ spotObj.content }}</div>
</div>
<img v-if="spotObj.sources[0]?.thumbnail" class="inspect-img"
:src="spotObj.sources[0]?.thumbnail" />
</div>
</div>
<div class="remark" ref="followEle" v-if="returnSum > 0" @click="openInspectPop">
<div class="head flexacenter">
<div class="text">寄托回访</div>
<div class="more flexacenter">
{{ returnSum }}
<img class="icon" src="@/assets/img/publicImage/arrow-black-down.svg" />
</div>
</div>
<div class="return-visit flexacenter" scroll-x>
<template v-for="(item, index) in remarkList" :key="index">
<div class="item flex1" v-if="item.typeid == 2">
<div class="info flexacenter">
<img class="avatar" :src="item.avatar" />
<div class="username">{{ item.nickname }}</div>
<div class="label flexacenter">已入住</div>
</div>
<div class="explain two-line-text">{{ item.content }}</div>
</div>
</template>
</div>
</div>
<!-- 房间类型 -->
<div class="type-box" v-if="roomList.length !== 0" ref="roomEle">
<div class="item flexacenter" v-for="(item, index) in roomList" :key="index">
<div class="media" v-if="item.thumbnail"
@click="cloaseImageShow([...item['videos'], ...item['images']], 0, `media${index}`)">
<img class="icon" :src="item.thumbnail" />
<img v-if="item.isVideo" class="play" src="@/assets/img/publicImage/video-icon.svg" />
</div>
<div class="info flex1">
<div class="name">{{ item.name }}</div>
<div class="tags flexflex" v-if="item.allowance || (item.tags && item.tags.length > 0)">
<div class="tags-item green" v-if="item.allowance">剩余{{ item.allowance }}</div>
<div class="tags-item" v-for="(item, index) in item.tags" key="index">{{ item }}
</div>
</div>
<div class="price flexflex">
<div class="unit">HK$</div>
<div class="number">{{ item.discountprice || item.price }}</div>
<div class="month">/</div>
<div class="original" v-if="item.discountprice">HK$ {{ item.price }}/</div>
</div>
</div>
<div class="btn flexacenter">
<div v-if="item.iscollection" class="collect flexcenter red"
@click="roomCollect(item.id, index)">
<img class="icon" src="@/assets/img/apartmentDetail/collect-red.svg" />
已收藏
</div>
<div v-else class="collect flexcenter" @click="roomCollect(item.id, index)">
<img class="icon" src="@/assets/img/apartmentDetail/collect-hollow-black.svg" />
收藏
</div>
<div v-if="item.status == 1" class="consult flexcenter" @click="modificationContact">
<img class="icon" src="@/assets/img/apartmentDetail/consult-icon.png" />
咨询
</div>
<div v-else class="full-rent flexcenter">已租满</div>
</div>
</div>
<!-- <div class="type-item flexacenter" v-for="(item, index) in roomList" :key="index">
<div class="type-left flex1">
<div class="type-name">{{ item["name"] }}</div>
<div class="type-tags flexacenter" v-if="item.allowance || item.tags.length != 0">
<div class="tags-item flexcenter first" v-if="item.allowance">仅剩{{ item.allowance }}</div>
<template v-if="item.tags.length != 0">
<div class="tags-item flexcenter" v-for="(it, ii) in item.tags" :key="ii">{{ it }}</div>
</template>
</div>
<div class="media-box flexflex" v-if="item.videos.length != 0 || item.images.length != 0">
<div class="media-btn flexcenter" @click="handleMediaBtn('left', index)" v-if="item.videos.length + item.images.length > 5">
<img v-if="mediaBtnstate[index] && mediaBtnstate[index] != 0" class="rotate180 arrow" src="@/assets/img/publicImage/black-arrow.svg" alt="" />
<img v-else class="arrow" src="@/assets/img/publicImage/gray-arrow.svg" alt="" />
</div>
<div class="media-list flexacenter no-scrollbar" :class="`element${index}`">
<div class="media-item flexcenter" v-for="(it, i) in item['videos']" :key="i" @click="cloaseImageShow([...item['videos'], ...item['images']], i, `media${index}`)">
<img class="media-img" :alt="`${item['name']}的视频图`" v-lazy="it['thumbnail']" />
<img class="media-icon" src="@/assets/img/apartmentDetail/media-icon.svg" />
</div>
<div class="media-item flexcenter" v-for="(it, i) in item['images']" :key="i" @click="cloaseImageShow([...item['videos'], ...item['images']], item['videos'].length + i, `media${index}`)">
<img class="media-img" :alt="`${item['name']}的详情图`" v-lazy="it['thumbnail']" />
</div>
</div>
<div class="media-btn flexcenter" @click="handleMediaBtn('right', index)" v-if="item.videos.length + item.images.length > 5">
<img v-show="mediaBtnstate[index] != 1" class="arrow" src="@/assets/img/publicImage/black-arrow.svg" alt="" />
<img v-show="mediaBtnstate[index] == 1" class="rotate180 arrow" src="@/assets/img/publicImage/gray-arrow.svg" alt="" />
</div>
</div>
</div>
<div class="type-right flexacenter">
<div class="price-box flexflex">
<div class="former" v-if="item['discountprice']">HK$ {{ item["price"] }}/</div>
<div class="new flexacenter">
<div class="unit">HK$</div>
<div class="cost">{{ item["discountprice"] || item["price"] }}</div>
/
</div>
</div>
<div class="consult-btn flexcenter" :aria-label="`${item['name']}-咨询按钮`" v-if="item['status'] == 1" @click="modificationContact">咨询</div>
<div class="full-occupancy flexcenter" v-else>已租满</div>
</div>
</div> -->
</div>
<!-- 费用说明 -->
<div class="details-item cost" v-if="costList.length > 0" ref="costEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/cost-icon.png" />
费用说明
</div>
<div class="cost-box">
<div class="item" v-for="(item, index) in costList" :key="index">
<div class="head flexacenter">
<img class="icon" :src="require('@/assets/img/apartmentDetail/' + item.img)" />
{{ item.name }}
</div>
<div class="explain">{{ item.text }}</div>
</div>
</div>
</div>
<!-- 优惠活动 -->
<!-- <div class="details-item special-offer" v-if="info['promotionalactivities']" ref="specialEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/special-offer.svg" />
优惠活动
</div>
<div class="text" v-html="info['promotionalactivities']"></div>
</div> -->
<!-- 地址 -->
<div class="details-item location" v-if="info.address" ref="addressEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/bus-icon.svg" />
{{ info.location || "交通" }}
</div>
<view-map ref="viewMapRef"
:latlng="{ latitude: info['coordinate'][0], longitude: info['coordinate'][1] }"
:name="info['address']"></view-map>
<template v-if="false">
<el-popover :width="814" trigger="click" popper-style="padding: 0" :show-arrow="false"
v-model:visible="showDistance">
<template #reference>
<div class="annex-school-box flexacenter">
<div class="annex-left flex1 flexacenter">
<div class="annex-school-item flexflex flex1" @click="selectIndex()"
v-if="specialSchoolDistance">
<div class="distance-item-value special flexacenter">
<div class="mileage">{{ specialSchoolDistance.distanceText }}</div>
<img v-if="specialSchoolDistance.toolText == '步行'" class="tool-icon"
src="@/assets/img/detail/walk-icon.png" />
<img v-else class="tool-icon"
src="@/assets/img/apartmentDetail/subway-icon.png" />
<div class="tool-time">{{ specialSchoolDistance?.durationText }}
</div>
</div>
<div class="flexcenter">
<img src="@/assets/img/detail/markIcon.svg" class="marker-icon"
alt="" />
</div>
<div class="alias-text flexcenter">{{ specialSchoolDistance.alias ||
"都大" }}</div>
</div>
<div class="annex-school-item flexflex flex1"
v-for="(item, index) in annexSchoolOmit" :key="index"
@click="selectIndex(item.id)">
<div class="distance-item-value flexacenter">
<div class="mileage">{{ item.distanceText || "2.0km" }}</div>
<img v-if="!item.list[0].publictransport" class="tool-icon"
src="@/assets/img/detail/walk-icon.png" />
<img v-else class="tool-icon"
src="@/assets/img/apartmentDetail/subway-icon.png" />
<div class="tool-time">{{
item.list[0]?.publictransport?.durationText2 ||
item.list[0]?.durationText2 || "41min" }}</div>
</div>
<div class="flexcenter">
<img src="@/assets/img/detail/markIcon.svg" class="marker-icon"
alt="" />
</div>
<div class="alias-text flexcenter">{{ item.alias || "都大" }}</div>
</div>
<div class="line-img"></div>
</div>
<div class="annex-btn flexcenter">
<img class="annex-btn-bj" src="@/assets/img/detail/infoBtnBg.svg" />
更多
<img class="annex-btn-icon" src="@/assets/img/detail/arrowIcon.svg" />
</div>
</div>
</template>
<div class="distance-info-box pos-r"
:style="{ height: `${50 * annexSchoolList.length + 70}px` }">
<div class="title-box dis-f al-item jus-x">
房源
<img class="distance-arrow" src="@/assets/img/detail/arrow-circle-blue.svg" />
院校
<img src="../assets/img/detail/close.png" class="close-icon"
@click="showDistance = false" alt="" />
</div>
<div class="distance-info-data dis-f">
<div class="distance-info-left">
<div class="distance-info-left-item flexcenter"
:class="{ pitch: index == academyPitchIndex }"
v-for="(item, index) in annexSchoolList" :key="index"
@click="selectAcademyIndex(index)">{{ item.alias }}</div>
</div>
<el-scrollbar ref="elscrollbarRef"
:style="{ height: 50 * annexSchoolList.length + 'px' }">
<div class="distance-info-right flex1">
<div class="distance-header-box flexacenter">
<div class="flexacenter">
<div class="distance-header-icon flexcenter">
<img src="@/assets/img/detail/home.png" alt=""
class="distance-header-img" />
</div>
{{ targetAcademyPitch.school }}
</div>
<div class="distance-header-hint">本数据来自高德地图仅供参考</div>
</div>
<div class="academy-school-item"
v-for="(item, index) in targetAcademyPitch.list" :key="index">
<div class="academy-school-item-header flexacenter">
<div class="academy-school-item-left flexacenter">
<div class="academy-school-item-name">{{ item.title }}</div>
<div class="academy-school-item-number">{{ item.distanceText
|| "" }}</div>
</div>
<div class="academy-school-item-right flexacenter">
<img v-if="item.publictransport"
class="academy-school-item-icon"
src="@/assets/img/apartmentDetail/subway-icon.png" />
<img v-else class="academy-school-item-icon"
src="@/assets/img/detail/walk-icon.png" />
<div class="academy-school-item-time">{{
item?.publictransport?.durationText
|| item.durationText || "" }}</div>
</div>
<img class="arrow-green"
src="@/assets/img/detail/arrow-green.svg" />
</div>
<div class="academy-school-item-journey"
v-if="item.publictransport">
<div class="journey-item flexacenter"
v-for="(item, index) in item.publictransport.segments"
:key="index">
<div class="circle"></div>
<!-- 步行 骑行 -->
<div v-if="item.type == 'walking'"
class="journey-value flex1">步行{{
item.distanceText }}</div>
<!-- 地铁 -->
<div v-else-if="item.type == 'bus' && item.bustype == '地铁线路'"
class="journey-value flex1 subway flexacenter">
<div class="subway-name flexcenter">{{ item.name }}
</div>
<div class="flex1" style="white-space: nowrap">{{
item.via_num }}·{{
item.durationText }}</div>
</div>
<!-- 公交 -->
<div v-else-if="item.type == 'bus' && item.bustype == '普通公交线路'"
class="journey-value flex1 bus flexacenter">
<div class="bus-name flexcenter">{{ item.name }}</div>
<div class="flex1" style="white-space: nowrap">{{
item.via_num }}·{{
item.durationText }}</div>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</div>
</el-popover>
</template>
<!-- 交通 -->
<div class="traffic-box" v-if="info['traffic']">
<!-- <div class="traffic-title item-title flexcenter">交通</div> -->
<div class="traffic-content" v-html="info['traffic']"></div>
</div>
</div>
<!-- 公寓设施 -->
<div class="details-item apartment-facilities" :class="{ hide: isHideFacilities }"
v-if="facilitylist.length > 0" ref="facilitiesEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/apartment-facilities.svg" />
公寓设施
</div>
<div class="facility-box">
<div class="item" v-for="(item, index) in facilitylist" :key="index">
<div class="head">{{ item.name }}{{ item.label.length }}</div>
<div class="label flexflex">
<div class="label-item flexacenter" v-for="(item, index) in item.label"
:key="index">
<img class="icon"
src="@/assets/img/apartmentDetail/tick-circle-baby-blue.svg" />
{{ item }}
</div>
</div>
<div class="img-box flexflex" v-if="item.images.length > 0">
<div class="img-item" v-for="(item, index) in item.images" :key="index"
@click="openFacilitiesImg(item.imageurl)">
<img class="icon" :src="item.thumbnail" />
<div class="name">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div class="bottom-btn flexcenter" v-if="isHideFacilities" @click="openFacilities">
展开全部
<img class="icon" src="@/assets/img/publicImage/arrow-black-solid.svg" />
</div>
</div>
<!-- 房源详情 -->
<div class="details-item details-message" :class="{ hide: isHideDetails }" v-if="info['message']"
ref="messageEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/listing-details.png" />
房源详情
</div>
<div class="text" v-html="info['message']"></div>
<div class="bottom-btn flexcenter" v-if="isHideDetails" @click="openDetails">
展开全部
<img class="icon" src="@/assets/img/publicImage/arrow-black-solid.svg" />
</div>
</div>
<!-- 公寓设施 -->
<!-- <div class="details-item apartment-facilities" v-if="info['facilities']" ref="facilitiesEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/apartment-facilities.svg" />
公寓设施
</div>
<div class="facilities-box">
<template v-for="(item, key) of facilitiesKeyValue" :key="key">
<div class="facilities-item flexflex" v-if="info['facilities'][key] && info['facilities'][key].length != 0">
<div class="facilities-header flexflex">
<div class="item-title">{{ item }}</div>
</div>
<div class="facilities-list flexflex flex1">
<div class="item flexcenter" v-for="(it, i) in info['facilities'][key]" :key="i">
<img class="icon" src="@/assets/img/apartmentDetail/tick-green.svg" />
{{ it }}
</div>
</div>
</div>
</template>
</div>
</div> -->
<!-- 生活 -->
<div class="details-item life" v-if="info['life']" ref="lifeEle">
<div class="details-header flexacenter">
<img class="icon" src="@/assets/img/apartmentDetail/live.png" />
生活配套
</div>
<div class="text" v-html="info['life']"></div>
</div>
<!-- 品牌介绍 -->
<div class="details-item company" v-if="company" ref="companyEle">
<div class="details-header flexacenter">
<img class="icon" style="width: 20px; height: 20px"
src="@/assets/img/apartmentDetail/introduce-icon.png" />
品牌介绍
</div>
<img class="company-img flexflex" v-lazy="company['imageurl']" />
<div class="text" v-html="company['introduction']"></div>
</div>
<div class="details-item hint-box">
<div class="hint-item">
温馨提示房源信息均由公寓方/酒店提供并对其真实性合法性等负责平台不负责甄别和审核具体内容真实性和有效性等请务必仔细核实相关信息谨防上当受骗</div>
<div class="hint-item"></div>
<div class="hint-item flexacenter">公寓/酒店/中介房源推广合作请联系<a
@click="copy('ad@gter.net')">ad@gter.net</a>
</div>
</div>
</div>
</div>
<div class="details-right flexacenter" ref="detailsRigth">
<phoneqrcode type="apartment" :qrcode="qrcode"></phoneqrcode>
<groupqrcode type="apartment"></groupqrcode>
<!-- 同品牌公寓 -->
<div class="same-brand-title flexcenter" v-if="dualBrandList.length != 0" ref="eleseEle">
<img class="same-brand-icon" src="@/assets/img/apartmentDetail/same-brand.png" />
同品牌其他公寓
</div>
<same-brand-item v-for="(item, index) in dualBrandList" :item="item" :key="index"></same-brand-item>
<div class="same-brand-title like flexcenter" v-if="likeList.length != 0">
<img class="same-brand-icon" src="@/assets/img/apartmentDetail/same-brand-recommendation.png" />
猜你喜欢
</div>
<same-brand-item v-for="(item, index) in likeList" :item="item" :key="index"></same-brand-item>
</div>
</div>
</div>
<!-- 添加客服 - 弹窗 -->
<div class="add-customer-mask flexcenter" v-if="contactReservationState">
<div class="add-customer-box flexcenter" :class="{ two: customerservicelist.length != 1 }">
<img class="close" @click="modificationContact" src="@/assets/img/publicImage/circle-close.png" />
<img class="add-customer-violet" src="@/assets/img/apartmentDetail/add-customer-violet.svg" />
<img class="add-customer-violet violet2" src="@/assets/img/apartmentDetail/add-customer-violet2.svg" />
<div class="add-customer-interior flexflex">
<img class="add-customer-interior-bj bj2" src="@/assets/img/apartmentDetail/add-customer-map2.png" />
<img class="add-customer-interior-bj bj2"
src="@/assets/img/apartmentDetail/add-customer-interior-bj2.svg" />
<img class="add-customer-interior-bj" src="@/assets/img/apartmentDetail/add-customer-map.svg" />
<img class="add-customer-interior-bj" src="@/assets/img/apartmentDetail/add-customer-interior-bj.svg" />
<img class="title" src="@/assets/img/apartmentDetail/add-customer-title.png" />
<div class="QR-code-list flexflex">
<div class="QR-code-item" v-for="(item, index) in customerservicelist" :key="index">
<div class="QR-code-box flexcenter">
<div class="top-right-corner"></div>
<div class="bottom-left-corner"></div>
<div class="bottom-right-corner"></div>
<div class="QR-code-chunk flexcenter">
<img class="QR-code-img" :src="item['image']" />
</div>
</div>
<div class="name flexcenter">{{ item["title"] }}</div>
<div class="hint flexcenter">微信扫码添加备注寄托</div>
</div>
</div>
</div>
</div>
</div>
<footerpage></footerpage>
<back-to-top></back-to-top>
<!-- 选择院校弹窗 -->
<el-dialog v-model="isSelectSchool" width="600" class="selectSchoolPop" :show-close="false">
<div class="title-box dis-f al-item jus-x">
选择院校
<img src="@/assets/img/detail/close.png" class="close-icon" @click="isSelectSchool = false" />
</div>
<div class="site flexacenter" bind:tap="to_map">
<img class="site-img" src="@/assets/img/apartmentDetail/orientation.png" />
<div class="site-text flex1">{{ info.address }}</div>
<div class="btn flexacenter" @click="openMap">
地图
<img class="btn-img" src="@/assets/img/apartmentDetail/arrow-black.svg" />
</div>
</div>
<div class="select-list" ref="selectSchoolRef">
<div class="select-item flexflex" :class="['item' + item.sid, { pitch: distancePitch.sid == item.sid }]"
v-for="(item, index) in distanceList" :key="index" @click.stop="selectSchool(item.sid)">
<div class="select-icon flexcenter">
<img class="select-img" src="@/assets/img/apartmentDetail/u1834.png" />
</div>
<div class="select-info flex1">
<div class="select-name" v-if="item.name">{{ item.name }}</div>
<div class="distance flexacenter">
距离
<div class="abbreviation">{{ item.alias }}</div>
<template v-if="item.distance > 0">{{ item.distance }}km</template>
</div>
<div class="vehicle flexflex flex1" v-if="distancePitch.distanceArr?.length > 0">
<div class="item flexcenter" v-for="(item, index) in item.distanceArr" :key="index">{{
item.text + item.duration }}</div>
<!-- <div class="item flexcenter" v-if="item.walking_duration > 0">
步行{{ calculateDuration(item.walking_duration) }}
</div>
<div class="item flexcenter" v-if="item.driving_duration > 0">
地铁{{ calculateDuration(item.driving_duration) }}
</div>
<div class="item flexcenter" v-if="item.transit_duration > 0">
巴士{{ calculateDuration(item.transit_duration) }}
</div> -->
</div>
</div>
<img class="tick" v-if="distancePitch.sid == item.sid"
src="@/assets/img/apartmentDetail/tick-circle-red.svg" />
</div>
</div>
</el-dialog>
<el-dialog v-model="isInspectPop" width="600" class="inspectPop" :show-close="false">
<div class="type flexacenter">
<div class="item" :class="{ pitch: remarkTypeid == 0 }" @click="cutRemarkType(0)">全部 {{ spotSum + returnSum
}}
</div>
<div class="item" :class="{ pitch: remarkTypeid == 1 }" @click="cutRemarkType(1)">寄托实地考察 {{ spotSum }}</div>
<div v-if="returnSum > 0" class="item" :class="{ pitch: remarkTypeid == 2 }" @click="cutRemarkType(2)">寄托回访
{{
returnSum }}</div>
</div>
<div class="list">
<template v-for="(item, index) in remarkList" :key="index">
<div class="item flexflex"
v-if="remarkTypeid == 0 || (remarkTypeid == 1 && item.typeid == 1) || (remarkTypeid == 2 && item.typeid == 2)">
<img class="avatar" :src="item.avatar" />
<div class="remark-content flexflex flex1">
<div class="type flexacenter" v-if="item.typeid == 1">
<img class="image" src="@/assets/img/publicImage/inspect-icon.png" />
寄托实地考察
</div>
<div class="type flexacenter" v-else>
<img class="image" src="@/assets/img/publicImage/return-visit.png" />
寄托回访
</div>
<div class="username flexacenter">
{{ item.nickname }}
<div class="checked-in" v-if="item.typeid == 2">已入住</div>
<img class="image" v-else-if="item.groupimage" :src="item.groupimage" />
</div>
<div class="date">{{ item.date }}</div>
<div class="text">{{ item.content }}</div>
<div class="media" scroll-x>
<div class="media-item" v-for="(item, i) in item.sources" :key="i"
@click="previewImg(index, i)">
<img class="media-img" :src="item.thumbnail" />
<img class="media-icon" v-if="item.type == 'videos'"
src="@/assets/img/publicImage/video-icon.svg" />
</div>
</div>
</div>
</div>
</template>
</div>
</el-dialog>
</template>
<script setup>
import { ref, onMounted, onUnmounted, toRefs, watch, getCurrentInstance, nextTick } from "vue";
import { ElMessage, ElDialog } from "element-plus";
import { useStore } from "vuex";
import pageTopBar from "../components/pageTopBar/pageTopBar.vue";
import footerpage from "@/components/footer/footer.vue";
import viewMap from "@/components/public/viewMap.vue";
import transmitBtn from "@/components/public/transmitBtn.vue";
import backToTop from "@/components/public/backToTop.vue";
import imageWatch from "@/components/detail/imageWatch.vue";
import phoneqrcode from "@/components/public/phoneQRcode.vue";
import groupqrcode from "@/components/public/group-QRcode.vue";
import sameBrandItem from "@/components/apartment/sameBrandItem.vue";
import api from "@/utils/api";
import { useRouter, useRoute } from "vue-router";
let router = useRouter();
const route = useRoute();
watch(route, () => {
uniqid = router.currentRoute.value.query["uniqid"];
pitchSchool = router.currentRoute.value.query["school"] || "";
info.value = {};
roomList.value = [];
carouselsconfig.value = { lives: {}, videos: {}, attachment: {} };
navList.value = [];
navTab.value = "specialEle";
dualBrandList.value = [];
contactReservationState.value = false;
customerservicelist.value = [];
mediaBtnstate.value = {};
slideshowList.value = null;
// detailsLeft.value = null;
token = "";
carouselIndex.value = 0;
allCarouselsData.value = [];
isHideFacilities.value = false;
facilitylist.value = [];
costList.value = [];
isHideDetails.value = false;
// mediaBtnstate.value = {}
init();
distanceSchool();
});
let { uniqid } = router.currentRoute.value.query;
let pitchSchool = route.query.school || 0;
import { copyToClipboard, metersToKilometers, secondsToHoursMinutes, calculateDuration, calculateDistance } from "@/utils/util.js";
const { proxy } = getCurrentInstance();
const store = useStore();
let imageShow = ref(false); // 查看大图弹窗的状态
let imageList = ref([]); // 查看大图弹窗的状态
let imageIndex = ref(0); // 查看大图弹窗的状态
let imageType = ""; // 查看大图弹窗的状态
const cloaseImageShow = (list, index, type) => {
if (list && imageType != type) {
imageList.value = list;
imageIndex.value = index;
imageType = type;
}
imageShow.value = !imageShow.value;
};
// 房间类型
let roomList = ref([]);
let info = ref({});
let facilitiesKeyValue = {
// 设施的键值对
public: "公共",
kitchen: "餐厨",
lavatory: "洗手间",
};
let withsameapartments = ref(0); // 其他公寓数量
let attachment = ref([]); // 轮播图数据
let company = {}; // 品牌数据
let token = "";
let qrcode = ref(""); // 小程序详情二维码
let allCarouselsData = ref([]);
const costArr = [
{
tab: "lease",
name: "租期",
img: "tenancy-term-icon.png",
},
{
tab: "payment",
name: "付款方式",
img: "payment-method-icon.png",
},
{
tab: "water",
name: "水电煤网",
img: "water-electricity-icon.png",
},
{
tab: "manage",
name: "管理费",
img: "management-cost-icon.png",
},
{
tab: "tax",
name: "印花税",
img: "stamp-duty-icon.png",
},
{
tab: "clean",
name: "房间清洁",
img: "clean-icon.png",
},
{
tab: "sublet",
name: "转租",
img: "sublet-icon.png",
},
];
// 公寓设施
const facilityKeyName = {
public: "公用设施",
service: "公寓服务",
sport: "运动&娱乐",
outdoor: "室外设施",
security: "安保设施",
room: "房间设施",
};
const facilityArr = ["room", "public", "service", "sport", "outdoor", "security"]; // 公寓设施 顺序
let facilitylist = ref([]);
let costList = ref([]);
let pitchScreenSchool = ref(0); // 选择的学校
onMounted(() => {
init();
distanceSchool();
let value = localStorage.getItem("apartmentPitchValue");
if (value) {
value = JSON.parse(value);
pitchScreenSchool = value.school;
}
});
const init = () => {
proxy.$get("https://api.gter.net/v1/apartment/details", { uniqid }).then((res) => {
if (res.code != 200) {
ElMessage.error(res["message"]);
return;
}
let data = res.data;
data["roomList"].forEach((element) => {
element["images"].forEach((element) => {
element["type"] = "attachment";
});
element["videos"].forEach((element) => {
element["type"] = "videos";
});
});
const roomListTarget = data.roomList || [];
roomListTarget.forEach((element) => {
let thumbnail = "";
let isVideo = false;
if (element.videos && element.videos.length > 0) {
thumbnail = element.videos[0].thumbnail;
isVideo = true;
} else if (element.images && element.images.length > 0) thumbnail = element.images[0].thumbnail;
element["thumbnail"] = thumbnail;
element["isVideo"] = isVideo;
});
roomList.value = roomListTarget;
data["info"]["coordinate"] = data["info"]["coordinate"].split(",").map((item) => {
return +item;
});
const facility = data.info.facility;
if (facility) {
// facility["public"][0].images = ["https://oss.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-5skdX7qqsgFptxhXa6QWi2uePJ5Bg8WFLPIqoYV7MtZCjvF5wr_-kU8uRQ0NDI5", "https://oss.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-5skfHvqqsgFptxhXa6QWi2uePJ5Bg8WFLPIqoYV7MsIAzvG5Ar_-kU8uRQ0NDI5"];
// facility["public"][1].images = ["https://oss.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-5skdXfqqsgFptxhXa6QWi2uePJ5Bg8WFLPIqoYV7MtdDG6W5Q7_-kU8uRQ0NDI5", "https://oss.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-5skfXnqqsgFptxhXa6QWi2uePJ5Bg8WFLPIqoYV7MtcA2vF5A7_-kU8uRQ0NDI5"];
let list = [];
facilityArr.forEach((key) => {
const target = facility[key];
let label = [];
let images = [];
if (Array.isArray(target)) {
target.forEach((element) => {
label.push(element.name);
if (element.images.length > 0) {
element.images.forEach((e) => {
images.push({
name: element.name,
imageurl: e.imageurl,
thumbnail: e.thumbnail,
});
});
}
});
// target.forEach((element) => {
// label.push(element.name);
// if (element.images.length > 0)
// images.push({
// name: element.name,
// img: element.images,
// });
// });
}
if (label.length > 0) {
list.push({
key,
name: facilityKeyName[key],
label,
images,
});
}
});
facilitylist.value = list;
}
// data.info.feedescription['sublet'] = data.info.feedescription['clean']
const feedescription = data.info.feedescription;
if (feedescription) {
let list = [];
costArr.forEach((element) => {
if (feedescription[element.tab]) {
list.push({
text: feedescription[element.tab],
...element,
});
}
});
costList.value = list;
}
info.value = data["info"];
attachment.value = data["info"]["attachment"];
withsameapartments.value = data["withsameapartments"];
company = data["company"];
token = data["token"];
qrcode.value = data["qrcode"];
handleAllCarouselsData();
document.title = data?.info?.title || "港校租房-品牌公寓详情";
nextTick(() => {
handleNavData();
getFacilitiesHeight();
getDetailsHeight();
getLikeList();
getMapDistance();
// if (info.value.fieldtrips == 1)
getComment();
});
if (data.withsameapartments > 0) dualBrandData();
});
};
let isHideDetails = ref(false);
// 获取详情的高度
const getDetailsHeight = () => {
let target = messageEle.value;
if (target) {
let height = target.offsetHeight;
if (height > 820) isHideDetails.value = true;
}
};
// 切换 详情 显示
const openDetails = () => {
isHideDetails.value = false;
let target = messageEle.value;
let header = target.querySelector(".details-header");
let box = target.querySelector(".text");
if (target) target.style.height = header.offsetHeight + box.offsetHeight + "px";
};
let isHideFacilities = ref(false);
// 获取设施的高度
const getFacilitiesHeight = () => {
let target = facilitiesEle.value;
if (target) {
let height = target.offsetHeight;
if (height > 820) isHideFacilities.value = true;
}
};
// 切换 设施 显示
const openFacilities = () => {
isHideFacilities.value = !isHideFacilities.value;
let target = facilitiesEle.value;
let header = target.querySelector(".details-header");
let box = target.querySelector(".facility-box");
if (target) target.style.height = header.offsetHeight + box.offsetHeight + "px";
};
// 点击打开设施图片预览
const openFacilitiesImg = (current) => {
const list = facilitylist.value || [];
let urls = [];
list.forEach((element) => {
element.images.forEach((ele) => {
urls.push({
imageurl: ele.imageurl,
thumbnail: ele.thumbnail,
type: "attachment",
});
});
});
const index = urls.findIndex((item) => item.imageurl == current);
cloaseImageShow(urls, index, "facilities");
};
let carouselsconfig = ref({ lives: {}, videos: {}, attachment: {} });
// 处理 轮播图大图的索引 tab
const handleAllCarouselsData = () => {
let targetInfo = { ...info.value };
let accumulativeTotal = 0; // 累计
for (const key in carouselsconfig.value) {
carouselsconfig.value[key] = {
amount: targetInfo[key].length,
index: accumulativeTotal,
};
accumulativeTotal += targetInfo[key].length;
}
targetInfo["lives"].forEach((element) => {
element["type"] = "lives";
allCarouselsData.value.push(element);
});
targetInfo["videos"].forEach((element) => {
element["type"] = "videos";
allCarouselsData.value.push(element);
});
let thumbnailList = targetInfo["thumbnailList"];
targetInfo["attachment"].forEach((element, index) => {
element = {
imageurl: element,
thumbnail: thumbnailList[index],
};
element["type"] = "attachment";
allCarouselsData.value.push(element);
});
};
const roomEle = ref(null);
const specialEle = ref(null);
const addressEle = ref(null);
const messageEle = ref(null);
const costEle = ref(null);
const facilitiesEle = ref(null);
const lifeEle = ref(null);
const companyEle = ref(null);
const eleseEle = ref(null);
const inspectEle = ref(null); // 实地考察 节点
const followEle = ref(null); // 寄托回访 节点
const viewMapRef = ref(null); // 地图组件
let navconfig = [
{
name: "优惠活动",
value: "specialEle",
},
{
name: "实地考察",
value: "inspectEle",
},
{
name: "寄托回访",
value: "followEle",
},
{
name: "房间类型",
value: "roomEle",
},
{
name: "费用说明",
value: "costEle",
},
{
name: "交通",
value: "addressEle",
},
{
name: "公寓设施",
value: "facilitiesEle",
},
{
name: "房源详情",
value: "messageEle",
},
{
name: "生活配套",
value: "lifeEle",
},
{
name: "品牌介绍",
value: "companyEle",
},
];
let navList = ref([]);
let navTab = ref("specialEle");
// 初始化 判断 navTab 值
const initNavTab = () => {
if (info.value.promotionalactivities) navTab.value = "specialEle";
else if (spotSum.value > 0) navTab.value = "inspectEle";
else if (returnSum.value > 0) navTab.value = "followEle";
else navTab.value = "roomEle";
};
// 处理 navList 数据
const handleNavData = () => {
navList.value = [];
navconfig.forEach((element) => {
if (eval(element["value"]).value) navList.value.push(element);
});
};
// 处理点击nav 滚动事件
const handleClickNav = (value) => {
const headerHeight = headerRef.value.getBoundingClientRect().height + 20 + 260 - 36 - 20;
let scrollTop = eval(value).value.offsetTop + headerHeight;
window.scrollTo({ top: scrollTop, behavior: "smooth" });
};
let dualBrandList = ref([]); // 同品牌数据
// 同品牌请求数据
const dualBrandData = () => {
// proxy.$get("/tenement/pc/api/apartment", { token }).then((res) => {
proxy.$get("https://api.gter.net/v1/apartment/lists", { token }).then((res) => {
if (res.code != 200) return;
let data = res.data;
dualBrandList.value = data.data;
});
};
// 点击转发的复制链接按钮
const copy = (value) =>
copyToClipboard(value).then(() => {
ElMessage({
message: "复制成功",
center: true,
offset: 320,
duration: 1000,
customClass: "message-info",
});
});
let contactReservationState = ref(false); // 联系预订客服的弹窗状态
let customerservicelist = ref([]); // 联系预订客服的弹窗状态
// 修改 客服 弹窗状态
const modificationContact = () => {
if (customerservicelist.value.length == 0) contactReservationService();
else contactReservationState.value = !contactReservationState.value;
};
// 联系预订客服 请求数据
const contactReservationService = () => {
proxy
.$get("/tenement/pc/api/apartment/customerservice", {
customerservice: info.value["customerservice"],
token,
})
.then((res) => {
if (res.code != 200) return;
let data = res.data;
customerservicelist.value = data["customerservicelist"];
contactReservationState.value = !contactReservationState.value;
});
};
let mediaBtnstate = ref({}); // 0 左边为不能点击 1 右边不能点击 2 是两个都能点击
const handleMediaBtn = (type, index) => {
const element = document.querySelector(`.element${index}`);
if (element) {
let left;
if (type == "left") left = element.scrollLeft - 86;
else left = element.scrollLeft + 86;
const scrollOptions = {
left,
behavior: "smooth",
};
element.scrollTo(scrollOptions);
nextTick(() => {
setTimeout(() => {
if (element.scrollLeft == 0) mediaBtnstate.value[index] = 0;
else if (element.scrollLeft + 460 == element.scrollWidth) mediaBtnstate.value[index] = 1;
else mediaBtnstate.value[index] = 2;
}, 150);
});
}
};
let slideshowList = ref(null); // 轮播图小图的节点
let carouselIndex = ref(0); // 轮播图的索引
const remarkCaruselUp = ref(null);
const carouselChange = (value) => {
carouselIndex.value = value;
const element = slideshowList.value;
const elementchild = element.querySelector(`.item${value}`);
let left = elementchild.offsetLeft - element.offsetLeft;
const scrollOptions = {
left,
behavior: "smooth",
};
element.scrollTo(scrollOptions);
};
// 顶部轮播图的左右按钮
const handleslideshow = (type) => {
if (type == "left" && carouselIndex.value != 0) remarkCaruselUp.value.prev();
if (type != "left" && carouselIndex.value != allCarouselsData.value.length - 1) remarkCaruselUp.value.next();
};
// 直接点击轮播图的小图就行切换
const slideshowItem = (index) => remarkCaruselUp.value.setActiveItem(index);
// 直接点击大图的 类型
const slideshowType = (type) => slideshowItem(carouselsconfig.value[type].index);
let detailsLeft = ref(null);
let detailsRigth = ref(null);
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
let isFixed = ref(false); // 详情左侧是否固定
let FixedBottom = ref(0); // 详情左侧固定的距离
let detailsBox = ref(null); // 详情的节点
let headerRef = ref(null); // 头部节点
let isOperateShow = ref(true); // 底部操作栏是否显示
const handleScroll = () => {
if (Math.random() > 0.3) return;
let body = document.documentElement ? document.documentElement : document.body ? document.body : document.querySelector(".element");
let offsetHeight = body.offsetHeight;
const clientHeight = document.documentElement.clientHeight;
const scrollTop = document.documentElement.scrollTop;
const top = scrollTop + clientHeight;
const headerHeight = headerRef.value.getBoundingClientRect().height + 20;
const left = detailsLeft.value.getBoundingClientRect();
const rigth = detailsRigth.value.getBoundingClientRect();
const leftHeight = left.height;
const rigthHeight = rigth.height;
if (leftHeight < rigthHeight) {
// 整个左边高度到顶部的距离
const topDistance = 260 - 36 + headerHeight + leftHeight + 223;
if (top > topDistance && isFixed.value == false) {
isFixed.value = true;
FixedBottom.value = document.querySelector(".index-footer").getBoundingClientRect().height + 20;
}
if (top < topDistance && isFixed.value == true) isFixed.value = false;
}
if (scrollTop + clientHeight >= offsetHeight - 200) isOperateShow.value = false;
else isOperateShow.value = true;
for (let i = 0; i < navList.value.length; i++) {
let element = navList.value[i];
const rect = eval(element.value).value.getBoundingClientRect();
const distanceToTop = rect.top;
if (distanceToTop >= 0) {
navTab.value = element.value;
break;
}
}
};
const indicateTypeState = () => {
let total = 0; // 累计
for (const key in carouselsconfig.value) {
if (carouselsconfig.value[key]["amount"] > 0) total++;
}
return total > 1 ? true : false;
};
// 公共跳转
const publicJump = (path) => router.push(path);
// 获取 距离学校距离
const distanceSchool = () => {
api.detailsDistance({
uniqid,
istype: 2,
}).then((res) => {
const data = res.data;
if (res.code != 200) return;
let specialSchoolDistanceTarget = null;
let academyPitchIndexTarget = 0;
const school = pitchSchool || "";
let annexSchoolOmitTarget = [];
data.forEach((element, index) => {
element["distanceText"] = metersToKilometers(element.distance);
element.list.forEach((ele) => {
ele["durationText"] = secondsToHoursMinutes(ele.duration, "chinese");
ele["durationText2"] = secondsToHoursMinutes(ele.duration);
ele["distanceText"] = metersToKilometers(ele.distance);
if (Object.prototype.toString.call(ele.publictransport) === "[object Object]") {
ele.publictransport["durationText"] = secondsToHoursMinutes(ele?.publictransport?.duration || 0, "chinese");
ele.publictransport["durationText2"] = secondsToHoursMinutes(ele?.publictransport?.duration || 0);
const segments = ele.publictransport["segments"];
if (Array.isArray(segments)) {
segments.forEach((e) => {
e["via_num"] = 1 + Math.floor(e.via_num);
e["durationText"] = secondsToHoursMinutes(e.duration);
e["distanceText"] = metersToKilometers(e.distance, "chinese");
});
}
} else ele.publictransport = null;
});
if (school == element.id) {
academyPitchIndexTarget = index;
const obj = element.list[0] || {};
let toolText = "步行";
if (Object.prototype.toString.call(obj.publictransport) === "[object Object]") toolText = "公交地铁";
specialSchoolDistanceTarget = {
alias: element.alias,
distanceText: obj["distanceText"],
durationText: obj.publictransport?.durationText2 || obj["durationText2"],
toolText,
};
} else annexSchoolOmitTarget.push(element);
});
if (specialSchoolDistanceTarget) annexSchoolOmitTarget = annexSchoolOmitTarget.slice(0, 4);
else annexSchoolOmitTarget = annexSchoolOmitTarget.slice(0, 5);
annexSchoolOmit.value = annexSchoolOmitTarget;
annexSchoolList.value = data;
targetAcademyPitch.value = data[academyPitchIndexTarget];
specialSchoolDistance.value = specialSchoolDistanceTarget;
academyPitchIndex.value = academyPitchIndexTarget;
});
};
let annexSchoolOmit = ref([]); // 附近院校 前面 7个 或者 6个的
let annexSchoolList = ref([]); // 附近院校数据
let academyPitchIndex = ref(0); // 附近学校距离选中院校 下标
let targetAcademyPitch = ref({}); // 附近学校距离选中院校 数据
let specialSchoolDistance = ref(null); // 特殊的 用户带有school参数 则需要特殊显示 学校距离
const elscrollbarRef = ref(null); // 滚动条
// 选择附近学校距离的学校下标
const selectAcademyIndex = (index) => {
academyPitchIndex.value = index || 0;
targetAcademyPitch.value = annexSchoolList.value[academyPitchIndex.value];
elscrollbarRef.value.setScrollTop(0);
};
// 选择附近学校距离的学校下标
const selectIndex = (id) => {
if (!id) id = pitchSchool;
const data = annexSchoolOmit.value || [];
data.forEach((element, index) => {
if (element.id == id) {
academyPitchIndex.value = index || 0;
targetAcademyPitch.value = annexSchoolList.value[academyPitchIndex.value];
}
});
// showDistance.value = true
};
//显示详情
let showDistance = ref(false);
// 点击收藏
const handleCollect = () => {
// api.apartmentCollection({ token }).then((res) => {
proxy.$post("https://api.gter.net/v1/apartment/collection", { token }).then((res) => {
if (res.code != 200) return;
const data = res.data;
info.value.iscollect = data.status || 0;
ElMessage.success(res.message);
});
};
// 设置媒体列表的类名
const getClass = (index) => {
return {
pitch: index === carouselIndex.value,
[`item${index}`]: true,
};
};
let likeList = ref([]); // 猜你喜欢 数据
// 获取猜你喜欢 数据
const getLikeList = () => {
proxy.$get("https://api.gter.net/v1/apartment/lists", { token, guess: 1 }).then((res) => {
if (res.code != 200) return;
let data = res.data;
likeList.value = data.data;
});
};
let isSelectSchool = ref(false); // 选择学校状态
let distancePitch = ref({});
let distanceList = ref([]);
const getMapDistance = () => {
proxy.$get("https://api.gter.net/v1/apartment/mapDistance", { token }).then((res) => {
if (res.code != 200) return;
const data = res.data || [];
const sid = pitchScreenSchool;
const distance = data.distance || [];
distance.sort((a, b) => a.distance - b.distance);
let arr = [];
let pitch = null;
distance.forEach((element) => {
// let obj = {};
// element.distance = calculateDistance(element.distance);
// if (element.walking && element.walking.duration) obj["walking"] = calculateDuration(element.walking.duration);
// if (element.transit && element.transit.duration) obj["transit"] = calculateDuration(element.transit.duration);
// if (element.driving && element.driving.duration) obj["driving"] = calculateDuration(element.driving.duration);
// const target = {
// name: element.name,
// distance: element.distance,
// alias: element.alias,
// sid: element.sid,
// obj,
// };
// arr.push(target);
const keyobj = {
metrobus_duration: "地铁巴士",
walking_duration: "步行",
driving_duration: "地铁",
transit_duration: "巴士",
}
const arr = ["metrobus_duration", "walking_duration", "transit_duration", "driving_duration"]
let valueArr = []
// element['metrobus_duration'] = 6
// element['walking_duration'] = 6
// element['transit_duration'] = 6
// element['driving_duration'] = 6
arr.forEach(key => {
if (element[key] > 0) {
const value = element[key]
valueArr.push({
text: keyobj[key],
value,
duration: calculateDuration(value),
})
}
})
element['distanceArr'] = valueArr.sort((a, b) => Number(a.value) - Number(b.value))
if (element.sid == sid) pitch = element;
});
if (pitch == null) pitch = distance[0];
distanceList.value = distance;
distancePitch.value = pitch;
});
};
const selectSchoolRef = ref(null);
const openSelectSchool = () => {
isSelectSchool.value = true;
nextTick(() => {
return;
const item = selectSchoolRef.value.querySelector(`.item${distancePitch.value.sid}`);
if (!item) return;
selectSchoolRef.value.scrollTo({ top: item.offsetTop - 20, behavior: "smooth" });
});
};
const selectSchool = (sid) => {
let list = distanceList.value;
distancePitch.value = list.find((item) => item.sid == sid);
isSelectSchool.value = false;
// 修改本地存储
let pitchValue = localStorage.getItem("apartmentPitchValue");
if (pitchValue) {
pitchValue = JSON.parse(pitchValue);
pitchValue.school = sid;
localStorage.setItem("apartmentPitchValue", JSON.stringify(pitchValue));
}
};
const openMap = () => {
viewMapRef.value.showPop();
isSelectSchool.value = false;
};
const roomCollect = (id, index) => {
proxy.$post("https://api.gter.net/v1/apartment/roomCollection", { id }).then((res) => {
if (res.code != 200) return;
const data = res.data;
ElMessage.success(res.message);
roomList.value[index].iscollection = data.status || 0;
});
};
let isInspectPop = ref(false);
let spotObj = ref({});
let spotSum = ref(0);
let returnSum = ref(0);
let remarkList = ref([]);
let remarkTypeid = ref(0); // 0 全部 1 考察 2 回访
const getComment = () => {
proxy
.$post("https://api.gter.net/v1/apartment/comment", { token })
.then((res) => {
if (res.code != 200) return;
let data = res.data || [];
let spot = null; // 实地考察
let sSum = 0;
let rSum = 0;
data.forEach((element) => {
element.date = formatDate(element.created_at);
// 合并视频和图片资源
element.sources = [
...(element.attachment.videos || []).map((video) => ({
type: "videos",
thumbnail: video.cover,
poster: video.cover,
videourl: video.url,
})),
...(element.attachment.images || []).map((image) => ({
type: "attachment",
imageurl: image.url,
thumbnail: image.cover,
})),
];
// 分类统计
element.typeid === 1 ? ((spot = element), sSum++) : rSum++;
});
spotObj.value = spot;
remarkList.value = data;
spotSum.value = sSum;
returnSum.value = rSum;
nextTick(() => {
handleNavData();
initNavTab();
});
})
.catch((err) => {
initNavTab();
});
};
const formatDate = (dateStr) => {
const date = new Date(dateStr);
const year = date.getFullYear();
const month = date.getMonth() + 1; // 月份从 0 开始
const day = date.getDate();
// 移除前导零(如果需要)
return `${year}.${month}.${day}`;
};
const openInspectPop = () => {
isInspectPop.value = true;
};
const previewImg = (index, i) => {
const list = remarkList.value || [];
// const url = e.currentTarget.dataset.url;
// if (url) {
// list.forEach((element, indexi) => {
// (element.sources || []).forEach((element, indexii) => {
// if (url == element.poster) {
// index = indexi;
// i = indexii;
// }
// });
// });
// }
const sources = list[index].sources || [];
cloaseImageShow(sources, i, "inspect");
};
// 切换点评
const cutRemarkType = (value) => (remarkTypeid.value = value);
</script>
<style lang="less" scoped>
/deep/ .returnTop {
bottom: 88px;
}
/deep/ body {
padding-bottom: 70px;
}
// 引入公共样式
@import "@/assets/styles/apartmentDetail.less";
</style>
<style lang="less">
.dis-f {
display: flex;
}
.jus-x {
justify-content: center;
}
.jus-sp {
justify-content: space-around;
}
.al-item {
align-items: center;
}
.pos-r {
position: relative;
}
.message-info {
background: #000000;
border-color: #000000;
color: #fff;
.el-message__content {
color: #fff;
}
.el-icon {
display: none;
}
}
.annex-school-box {
margin: 20px 30px 0;
cursor: pointer;
.annex-left {
width: 100%;
height: 101px;
border: 1px solid #f2f2f2;
border-radius: 12px 0 0 12px;
position: relative;
z-index: 1;
.annex-school-item {
flex-direction: column;
align-items: center;
.distance-item-value {
height: 24px;
padding: 0 8px;
background-color: rgba(246, 246, 246, 1);
border-radius: 29px;
width: fit-content;
&.special {
background: linear-gradient(to right, rgba(255, 255, 255, 0.8) -14%, rgba(80, 227, 194, 0.8) 100%);
}
.mileage {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
font-size: 14px;
color: #000000;
margin-right: 6px;
}
.tool-icon {
width: 14px;
height: 14px;
margin-right: 3px;
}
.tool-time {
font-size: 13px;
color: #333333;
}
}
}
.marker-icon {
margin: 9px 0 12px;
}
.line-img {
height: 2px;
width: 724px;
background-image: linear-gradient(to right, #d7d7d7 0%, #d7d7d7 50%, transparent 0%);
background-size: 4px 2px;
background-repeat: repeat-x;
position: absolute;
z-index: -1;
}
}
.annex-btn {
width: 90px;
height: 101px;
font-size: 14px;
color: #000000;
border-radius: 0 12px 12px 0;
position: relative;
z-index: 1;
.annex-btn-bj {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
.annex-btn-icon {
width: 16px;
height: 16px;
margin-left: 10px;
}
}
}
.distance-info-box {
.title-box {
font-weight: 650;
font-size: 20px;
color: #000000;
height: 70px;
border-bottom: 1px solid #ebebeb;
border-radius: 16px 16px 0 0;
.distance-arrow {
width: 24px;
height: 24px;
margin: 0 28px;
}
.close-icon {
width: 16px;
height: 16px;
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
}
.distance-info-data {
.distance-info-left {
width: 70px;
flex-direction: column;
border-right: 1px solid #ebebeb;
box-sizing: content-box;
.distance-info-left-item {
width: 100%;
height: 50px;
font-size: 14px;
color: #555555;
cursor: pointer;
&.pitch {
font-weight: 650;
color: #000000;
background: linear-gradient(to right, rgba(255, 255, 255, 0.8) -14%, rgba(80, 227, 194, 0.8) 100%);
}
&:last-of-type {
border-radius: 0 0 0 10px;
}
}
}
.distance-info-right {
padding: 30px 40px;
.distance-header-box {
color: #333333;
font-weight: 650;
font-size: 16px;
margin-bottom: 30px;
justify-content: space-between;
.distance-header-icon {
width: 30px;
height: 30px;
background-color: #fddf6d;
border-radius: 50%;
margin-right: 10px;
.distance-header-img {
width: 20px;
height: 20px;
}
}
.distance-header-hint {
color: #a09e9e;
font-size: 13px;
font-weight: 400;
}
}
.academy-school-item {
background-color: rgba(246, 246, 246, 1);
border-radius: 12px;
margin-bottom: 30px;
}
.academy-school-item-header {
font-size: 16px;
width: 670px;
height: 65px;
justify-content: space-between;
padding-left: 22px;
padding-right: 20px;
position: relative;
}
.academy-school-item-header .arrow-green {
width: 7px;
height: 12px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
.academy-school-item-name {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
color: #000000;
}
.academy-school-item-number {
color: rgb(51, 51, 51);
margin-left: 4px;
font-weight: 400;
font-family: "ArialMT", "Arial", sans-serif;
}
.academy-school-item-icon {
width: 16px;
height: 16px;
margin-right: 6px;
}
.academy-school-item-time {
color: #000;
font-weight: 650;
font-size: 14px;
}
.academy-school-item-journey {
border-top: 1px solid #ebebeb;
padding-top: 22px;
padding-left: 22px;
padding-bottom: 30px;
position: relative;
}
.journey-item {
position: relative;
z-index: 1;
}
.journey-item:not(:last-of-type) {
margin-bottom: 20px;
}
.academy-school-item-journey::after {
content: "";
position: absolute;
top: 0;
left: 26.4px;
width: 1px;
height: 100%;
display: block;
background-image: linear-gradient(to bottom, #aaaaaa 50%, transparent 50%);
background-size: 1px 4px;
/* 控制虚线的宽度和间距 */
}
.journey-item:first-of-type::after {
content: "";
display: block;
position: absolute;
top: -22px;
left: 0;
width: 9px;
height: calc(50% + 22px);
background-color: rgba(246, 246, 246, 1);
}
.journey-item:last-of-type::after {
content: "";
display: block;
position: absolute;
bottom: -30px;
left: 0;
width: 9px;
height: calc(50% + 30px);
background-color: rgba(246, 246, 246, 1);
}
.journey-item .circle {
width: 9px;
height: 9px;
border-radius: 50%;
background-color: #f6f6f6;
border: 1px solid #797979;
box-sizing: border-box;
margin-right: 20px;
z-index: 1;
}
.journey-item .journey-value {
color: #333;
font-size: 13px;
padding-right: 20px;
}
.journey-item .journey-value.subway {
color: #aaaaaa;
}
.journey-item .journey-value.subway .subway-name {
padding: 3.5px 11px;
background-color: rgba(51, 51, 51, 1);
border-radius: 10px;
color: #fff;
margin-right: 10px;
max-width: 570px;
}
.journey-item .journey-value.bus {
color: #aaaaaa;
}
.journey-item .journey-value.bus .bus-name {
padding: 3.5px 11px;
background-color: rgba(80, 227, 194, 0);
box-sizing: border-box;
border: 1px solid rgba(51, 51, 51, 1);
border-radius: 10px;
margin-right: 10px;
color: #333333;
max-width: 570px;
}
.academy-school-hint {
color: #a09e9e;
font-size: 13px;
margin-bottom: 20px;
}
}
}
}
.el-popper.is-light {
border-radius: 10px !important;
}
.selectSchoolPop {
border-radius: 16px;
overflow: hidden;
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0;
.title-box {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
font-style: normal;
font-size: 25px;
line-height: 26px;
color: #000000;
text-align: center;
padding-top: 25px;
padding-bottom: 16px;
.close-icon {
width: 20px;
height: 20px;
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}
}
.site {
height: 35px;
background-color: rgba(242, 242, 242, 1);
margin: 0 20px 24px;
border-radius: 210px;
padding-left: 10px;
.site-img {
width: 16px;
height: 16px;
margin-right: 6px;
}
.site-text {
font-size: 14px;
color: #000000;
}
.btn {
font-size: 13px;
color: #555555;
padding: 0 10px;
height: 100%;
cursor: pointer;
.btn-img {
width: 6px;
height: 11px;
margin-left: 8px;
}
}
}
.select-list {
max-height: 500px;
min-height: 300px;
overflow: auto;
position: relative;
.select-item {
padding: 10px 15px;
border-top: 1px dotted #d7d7d7;
cursor: pointer;
&.pitch {
background-color: rgba(245, 244, 249, 1);
.select-icon {
background-color: #fddf6d;
}
}
&:last-of-type {
// border-radius: 0 0 16px 16px;
}
.select-icon {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #f2f2f2;
align-self: flex-start;
margin-right: 10px;
.select-img {
width: 20px;
height: 20px;
}
}
.select-info {
.select-name {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
font-style: normal;
font-size: 16px;
color: #000000;
margin-bottom: 7px;
}
.distance {
font-size: 14px;
color: #555555;
// margin-top: 7px;
padding-bottom: 7px;
.abbreviation {
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
font-style: normal;
color: #000000;
margin: 0 4px;
}
}
.vehicle {
flex-wrap: wrap;
.item {
height: 20px;
line-height: 20px;
line-height: 20px;
background-color: rgba(255, 255, 255, 0);
border: 1px solid rgba(127, 127, 127, 1);
border-radius: 30px;
font-size: 14px;
color: #333333;
width: fit-content;
padding: 0 5px;
margin-top: 5px;
margin-right: 10px;
.vehicle-icon {
width: 16px;
height: 16px;
margin-right: 5px;
}
}
}
}
.tick {
width: 20px;
height: 20px;
align-self: center;
}
}
}
}
}
.inspectPop {
border-radius: 16px;
overflow: hidden;
word-break: break-word;
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0;
.type {
padding: 20px 20px;
border-bottom: 1px dotted #d7d7d7;
.item {
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
font-weight: 400;
font-style: normal;
font-size: 14px;
text-align: center;
line-height: 30px;
border-radius: 12px;
height: 30px;
background-color: rgba(235, 235, 235, 1);
padding: 0 12px;
margin-right: 15px;
cursor: pointer;
&.pitch {
background-color: rgba(249, 93, 93, 1);
color: #ffffff;
}
}
}
.list {
max-height: 600px;
overflow: auto;
.item {
padding-top: 20px;
padding-left: 20px;
&:not(:last-of-type) {
border-bottom: 1px #d7d7d7 dotted;
}
.avatar {
width: 30px;
height: 30px;
border-radius: 50%;
margin-right: 10px;
}
.remark-content {
flex-direction: column;
position: relative;
.type {
position: absolute;
top: 0;
right: 0;
font-size: 14px;
color: #555555;
height: 23px;
background: inherit;
background-color: #f2f2f2;
border-radius: 78px 0 0 78px;
padding: 0 6px;
border: none;
.image {
// width: 24px;
height: 16px;
margin-right: 4px;
}
}
.username {
// font-size: 24px;
font-size: 14px;
// color: #333333;
// margin-bottom: 4.5px;
color: #555;
margin-right: 10px;
// cursor: pointer;
margin-bottom: 4px;
.image {
// width: 72px;
height: 16px;
margin-left: 12px;
}
.checked-in {
height: 20px;
line-height: 20px;
background-color: rgba(246, 246, 246, 0);
border: 1px solid rgba(80, 227, 194, 1);
border-radius: 45px;
font-size: 12px;
color: #50e3c2;
padding: 0 8px;
margin-left: 12px;
}
}
.date {
color: #a3a3a3;
font-size: 13px;
margin-bottom: 3px;
}
.text {
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
font-weight: 400;
font-style: normal;
font-size: 14px;
line-height: 24px;
white-space: pre-line;
margin-bottom: 12px;
margin-right: 13px;
color: #333333;
}
.media {
white-space: nowrap;
overflow: auto;
width: 512px;
margin-bottom: 20px;
.media-item {
display: inline-flex;
margin-right: 8px;
position: relative;
cursor: pointer;
.media-img {
// width: 213px;
width: 105px;
height: 70px;
border-radius: 8px;
object-fit: cover;
}
.media-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
}
}
}
}
}
}
}
}
</style>