shinnosuke
4 天以前 5ce0d76154bf4c1d70543476cdfad9a30195b0f8
rencaika/pages/stay-benefits/stay-benefits.uvue
@@ -10,9 +10,14 @@
            <view class="capsule-space" :style="'width:' + navWidth + 'px'"></view>
         </view>
      </view>
      <scroll-view class="page-scroll" scroll-y lower-threshold="80" @scroll="handleScroll" @scrolltolower="loadMore">
      <scroll-view :show-scrollbar="false" class="page-scroll" scroll-y lower-threshold="80" @scroll="handleScroll" @scrolltolower="loadMore">
         <view class="hero">
            <image class="hero-image" :src="currentScene.image" mode="aspectFill" @error="replaceHeroImage"></image>
            <swiper v-if="scenes.length > 0" class="hero-swiper" :current="activeIndex < 0 ? 0 : activeIndex" :circular="scenes.length > 1" :duration="240" @change="handleHeroChange">
               <swiper-item v-for="(item, index) in scenes" :key="item.id">
                  <image class="hero-image" :src="item.image" mode="aspectFill" @error="replaceSceneImage(index)"></image>
               </swiper-item>
            </swiper>
            <image v-else class="hero-image" :src="currentScene.image" mode="aspectFill" @error="replaceHeroImage"></image>
            <view class="hero-shade"></view><text v-if="currentScene.name != ''"
               class="scene-name">{{currentScene.name}}</text><text v-if="scenes.length == 0"
               class="no-gallery">暂无图册内容</text><scroll-view v-else class="scene-scroll" scroll-x :show-scrollbar="false"
@@ -52,7 +57,7 @@
                  v-for="item in visibleServices" :key="item.id" @tap="openService(item)">
                  <image class="service-image" :src="item.image" mode="aspectFill"
                     @error="replaceServiceImage(item)"></image>
                  <view class="service-info"><text class="service-name">{{item.name}}</text><view v-if="item.available" class="discount-tags"><text v-for="tag in item.discountTags" :key="tag" class="discount-tag">{{tag}}</text></view><text v-else class="discount-tag">已下架</text>
                  <view class="service-info"><text class="service-name">{{item.name}}</text><view v-if="item.available" class="discount-tags"><text v-for="tag in item.discountTags" :key="tag" :class="tag == '券' ? 'coupon-tag' : 'discount-tag'">{{tag}}</text></view><text v-else class="discount-tag">已下架</text>
                     <view v-if="item.merchantType != 'apartment'" class="address">
                        <image src="/static/benefits-assets/map-pin.svg"></image><text>{{item.address}}</text>
                     </view>
@@ -77,7 +82,7 @@
   import { getStayTopic, getStayTopicMerchants } from '../../services/portal.uts'
   import { resolveApiUrl } from '../../services/http.uts'
   import { safeBack } from '../../utils/navigation.uts'
   import { getSession } from '../../services/session.uts'
   import { resolveTalentId } from '../../utils/talent-id.uts'
   const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10)
   const isSolidNav = ref(false); const windowWidth = ref(375); const fallbackImage = ''
@@ -90,7 +95,7 @@
      const capsule = uni.getMenuButtonBoundingClientRect(); const info = uni.getSystemInfoSync(); windowWidth.value = info.windowWidth; navTop.value = capsule.top; navHeight.value = capsule.height; navWidth.value = capsule.width; navRight.value = info.windowWidth - capsule.right
      // #endif
      if (options != null && options['previewState'] != null) { const state = options['previewState'] as string; failInitialOnce.value = state == 'initialError'; failLoadMoreOnce.value = state == 'loadMoreError' }
      const session = getSession(); if (session != null && session.talentId != null) talentId.value = session.talentId as string
      talentId.value = resolveTalentId()
      loadStayTopic(); loadInitialServices()
   })
   const readText = (primary : any, fallback : any) : string => {
@@ -131,6 +136,8 @@
   const retryInitialLoad = () => loadInitialServices(); const retryLoadMore = () => loadMore()
   const selectScene = (index : number) => { if (index < 0 || index >= scenes.value.length) return; activeIndex.value = index; currentScene.value = scenes.value[index]; sceneScrollLeft.value = index * 137 * windowWidth.value / 750 }
   const handleSceneScrollEnd = (event : any) => { let index = Math.round(event.detail.scrollLeft * 750 / windowWidth.value / 137); if (index < 0) index = 0; if (index >= scenes.value.length) index = scenes.value.length - 1; selectScene(index) }
   /** 主图左右滑动:滑动落点同步到缩略图、名称与描述。 */
   const handleHeroChange = (event : any) => { const current = event != null && event.detail != null && event.detail.current != null ? Number(event.detail.current) : -1; if (current < 0) return; selectScene(current) }
   const handleScroll = (event : any) => { isSolidNav.value = event.detail.scrollTop > 64 }
   const switchTab = (tab : string) => { if (activeTab.value == tab && hasLoaded.value) return; activeTab.value = tab; loadInitialServices() }
   const loadStayServices = (append : boolean) => {
@@ -164,16 +171,24 @@
            const merchantType = readText(item.merchantType, item['merchant_type'])
            const merchantName = readText(item.merchantName, item['merchant_name'])
            const discountText = readText(item.discountText, item['discount_text'])
            const configuredTags: any[] = item.benefitTags instanceof Array ? (item.benefitTags as any[]) : []
            const displayTags: any[] = activeTab.value == 'hotel'
               ? configuredTags.filter((tag: any) => String(tag).trim() != '惠')
               : configuredTags
            const hasConfiguredTags = item.benefitTags instanceof Array
            const configuredTags: any[] = hasConfiguredTags ? (item.benefitTags as any[]) : []
            const displayTags: string[] = []
            for (let tagIndex = 0; tagIndex < configuredTags.length; tagIndex++) {
               const tag = String(configuredTags[tagIndex]).trim()
               if (tag != '' && tag != '人才专享' && displayTags.indexOf(tag) < 0) displayTags.push(tag)
            }
            const hasCoupon = item.couponAvailable === true || Number(item.couponAvailable) == 1
            if (!hasConfiguredTags) {
               if (hasCoupon) displayTags.push('券')
               if (discountText != '' && discountText != '人才专享') displayTags.push(discountText)
            }
            next.push({
               id: '' + item.id,
               merchantType: merchantType != '' ? merchantType : activeTab.value,
               name: merchantName != '' ? merchantName : '\u4f4f\u5bbf\u670d\u52a1',
               discount: discountText != '' ? discountText : '\u4eba\u624d\u4e13\u4eab',
               discountTags: displayTags.length > 0 ? displayTags : [discountText != '' ? discountText : '\u4eba\u624d\u4e13\u4eab'],
               discountTags: displayTags,
               address: readText(item.address, item['merchant_address']),
               image: coverUrl != '' ? resolveApiUrl(coverUrl) : fallbackImage,
               available: readAvailable(item)
@@ -288,6 +303,14 @@
      width: 100%;
      height: 590rpx;
   }
   /* 主图轮播:与下方缩略图带同高同位,盖在原来的静态主图上。 */
   .hero-swiper {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 590rpx;
   }
   .hero-shade {
      position: absolute;
      left: 0;
@@ -303,7 +326,7 @@
      top: 245rpx;
      max-width: 650rpx;
      color: #FFFFFF;
      font-family: 'StayCalligraphy', 'STXingkai', 'KaiTi', cursive;
      font-family: 'FoodCalligraphyFull', 'StayCalligraphy', 'STXingkai', 'KaiTi', cursive;
      font-size: 48rpx;
      font-weight: normal;
      font-style: normal;
@@ -454,8 +477,19 @@
      box-sizing: border-box;
      padding: 3rpx 8rpx;
      border-radius: 5rpx;
      background-color: #FFF0EB;
      color: #FF6239;
      background-color: #FFF5E7;
      color: #FF7C2B;
      font-size: 21rpx;
      line-height: 29rpx;
   }
   .coupon-tag {
      display: block;
      width: fit-content;
      box-sizing: border-box;
      padding: 3rpx 8rpx;
      border-radius: 5rpx;
      background-color: #FFF0EC;
      color: #FF5D37;
      font-size: 21rpx;
      line-height: 29rpx;
   }