<template>
|
<view class="page">
|
<view class="custom-nav" :class="isSolidNav ? 'is-solid' : ''">
|
<view class="custom-nav-bg"></view>
|
<view class="safe-space" :style="'height:' + navTop + 'px'"></view>
|
<view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'">
|
<text class="page-title">人才权益</text>
|
<view class="capsule-space" :style="'width:' + navWidth + 'px'"></view>
|
</view>
|
</view>
|
|
<scroll-view class="content" scroll-y lower-threshold="80" @scroll="handleScroll" @scrolltolower="loadMoreBenefits">
|
<view class="hero">
|
<image class="hero-image" src="/static/benefits-assets/benefits-hero.jpg" mode="aspectFill"></image>
|
<view class="hero-shade"></view>
|
<view class="hero-bottom-fade"></view>
|
<view class="search-bar">
|
<view class="search-input-wrap" @tap="openSearch">
|
<image src="/static/benefits-assets/search.svg"></image>
|
<input class="search-input" placeholder="搜索" placeholder-class="search-placeholder" disabled />
|
</view>
|
<view class="search-button" @tap="openSearch"><text>搜索</text></view>
|
</view>
|
</view>
|
|
<view class="category-panel">
|
<view class="category-item" v-for="item in categories" :key="item.name" @tap="chooseCategory(item.name)">
|
<image :src="item.icon" mode="aspectFit"></image>
|
<text>{{item.name}}</text>
|
</view>
|
</view>
|
|
<view class="recommend-title">
|
<image src="/static/benefits-assets/recommend.png" mode="aspectFit"></image>
|
<text>推荐权益</text>
|
</view>
|
|
<view v-if="initialLoading" class="benefit-empty"><view class="loading-spinner"></view><text>正在加载推荐权益</text></view>
|
<view v-else-if="initialError" class="benefit-empty error-state"><text>推荐权益加载失败</text><text class="retry-text" @tap="retryInitialLoad">重新加载</text></view>
|
<view v-else-if="hasLoaded && visibleBenefits.length == 0" class="benefit-empty"><text>暂无推荐权益</text></view>
|
<view v-else-if="hasLoaded" class="benefit-list">
|
<view class="benefit-item" v-for="item in visibleBenefits" :key="item.id" @tap="openMerchant(item)">
|
<image class="merchant-image" :src="item.image" mode="aspectFill" @error="replaceImage(item)"></image>
|
<view class="benefit-info">
|
<text class="benefit-name">{{item.name}}</text>
|
<view v-if="item.tags != null && item.tags.length > 0" class="tag-row"><text v-for="tag in item.tags" :key="tag" :class="tag == '券' ? 'coupon-tag' : 'discount-tag'">{{tag}}</text></view>
|
<view class="address-row"><image src="/static/benefits-assets/map-pin.svg"></image><text>{{item.address}}</text></view>
|
</view>
|
</view>
|
</view>
|
<view v-if="loadingMore" class="loading-row"><view class="loading-spinner"></view><text>正在加载</text></view>
|
<view v-else-if="loadMoreError" class="loading-row load-error" @tap="retryLoadMore"><text>加载失败,点击重试</text></view>
|
<view v-else-if="hasLoaded && !hasMoreBenefits && visibleBenefits.length > 0" class="no-more"><view></view><text>已经到底了</text><view></view></view>
|
<view class="content-bottom"></view>
|
</scroll-view>
|
|
<view class="tab-bar">
|
<view class="tab" @tap="goHome"><view class="tab-icon-wrap"><image src="/static/home-assets/home-default.svg"></image></view><text>首页</text></view>
|
<view class="tab active"><view class="tab-icon-wrap active-icon"><image src="/static/home-assets/benefits-active.svg"></image></view><text>人才权益</text></view>
|
<view class="tab card-tab" @tap="goCard"><view class="card-icon"><image src="/static/home-assets/card.png"></image><text class="card-label">人才卡</text></view></view>
|
<view class="tab" @tap="goPolicy"><view class="tab-icon-wrap"><image src="/static/home-assets/policy.svg"></image></view><text>人才政策</text></view>
|
<view class="tab" @tap="goProfile"><view class="tab-icon-wrap"><image src="/static/home-assets/profile.svg"></image></view><text>个人中心</text></view>
|
</view>
|
</view>
|
</template>
|
|
<script setup lang="uts">
|
import { isApiConfigured } from '../../config/api.uts'
|
import { getMerchants } from '../../services/portal.uts'
|
import { resolveApiUrl } from '../../services/http.uts'
|
import { resolveTalentId } from '../../utils/talent-id.uts'
|
import { openMerchantDetail } from '../../utils/merchant-navigation.uts'
|
|
const navTop = ref(28)
|
const navHeight = ref(32)
|
const navWidth = ref(88)
|
const navRight = ref(10)
|
const isSolidNav = ref(false)
|
const fallbackImage = ''
|
const talentId = ref('')
|
const replaceImage = (item: any) => { if (item.image != '') item.image = '' }
|
|
onLoad((options: any) => {
|
// #ifdef MP-WEIXIN
|
const capsule = uni.getMenuButtonBoundingClientRect()
|
const systemInfo = uni.getSystemInfoSync()
|
navTop.value = capsule.top
|
navHeight.value = capsule.height
|
navWidth.value = capsule.width
|
navRight.value = systemInfo.windowWidth - capsule.right
|
// #endif
|
if (options != null && options['previewState'] != null) {
|
const previewState = options['previewState'] as string
|
failInitialOnce.value = previewState == 'initialError'
|
failLoadMoreOnce.value = previewState == 'loadMoreError'
|
}
|
talentId.value = resolveTalentId()
|
loadInitialBenefits()
|
})
|
|
const categories = [
|
{ name: '臻享礼遇', icon: '/static/benefits-assets/premium.png' },
|
{ name: '暖心食享', icon: '/static/benefits-assets/food.png' },
|
{ name: '舒心安居', icon: '/static/benefits-assets/stay.png' },
|
{ name: '畅行无忧', icon: '/static/benefits-assets/travel.png' },
|
{ name: '悦览花城', icon: '/static/benefits-assets/culture.png' },
|
{ name: '惠享好物', icon: '/static/benefits-assets/shopping.png' },
|
{ name: '怡乐生活', icon: '/static/benefits-assets/life.png' },
|
{ name: '全部', icon: '/static/benefits-assets/all.png' }
|
]
|
|
const allBenefits = [
|
{ id: 1, name: '乡菜郎·攀枝花特色延边菜', discount: '6折', address: '西区格里坪镇清香坪东街78号', image: '/static/common/image-placeholder.png' },
|
{ id: 2, name: '金府酒楼(西区店)', discount: '6折', address: '西区清香坪北街51号', image: '/static/common/image-placeholder.png' },
|
{ id: 3, name: '乡菜郎·攀枝花特色延边菜', discount: '6折', address: '西区格里坪镇清香坪东街78号', image: '/static/common/image-placeholder.png' },
|
{ id: 4, name: '金府酒楼(西区店)', discount: '6折', address: '西区清香坪北街51号', image: '/static/common/image-placeholder.png' },
|
{ id: 5, name: '百杏厨房(中环天地店)', discount: '8.8折', address: '东区炳三区新福路20号中环天地负一层', image: '/static/common/image-placeholder.png' },
|
{ id: 6, name: '香佰里鲜货火锅(万象城店)', discount: '8.8折', address: '东区攀枝花大道东段298号', image: '/static/common/image-placeholder.png' },
|
{ id: 7, name: '乡菜郎·攀枝花特色延边菜', discount: '6折', address: '西区格里坪镇清香坪东街78号', image: '/static/common/image-placeholder.png' },
|
{ id: 8, name: '金府酒楼(西区店)', discount: '6折', address: '西区清香坪北街51号', image: '/static/common/image-placeholder.png' },
|
{ id: 9, name: '百杏厨房(中环天地店)', discount: '8.8折', address: '东区炳三区新福路20号中环天地负一层', image: '/static/common/image-placeholder.png' },
|
{ id: 10, name: '香佰里鲜货火锅(万象城店)', discount: '8.8折', address: '东区攀枝花大道东段298号', image: '/static/common/image-placeholder.png' },
|
{ id: 11, name: '乡菜郎·攀枝花特色延边菜', discount: '6折', address: '西区格里坪镇清香坪东街78号', image: '/static/common/image-placeholder.png' },
|
{ id: 12, name: '金府酒楼(西区店)', discount: '6折', address: '西区清香坪北街51号', image: '/static/common/image-placeholder.png' }
|
]
|
const visibleBenefits = ref<any[]>([])
|
const loadedBenefitCount = ref(0)
|
const currentPage = ref(0)
|
const initialLoading = ref(true)
|
const loadingMore = ref(false)
|
const hasLoaded = ref(false)
|
const hasMoreBenefits = ref(true)
|
const initialError = ref(false)
|
const loadMoreError = ref(false)
|
const failInitialOnce = ref(false)
|
const failLoadMoreOnce = ref(false)
|
const mapMerchants = (records: any[]): any[] => {
|
const mapped: any[] = []
|
for (let index = 0; index < records.length; index++) {
|
const item: any = records[index]
|
const rawType = item.merchantType != null ? item.merchantType as string : 'food'
|
const merchantType = rawType == '餐饮服务' ? 'food' : rawType
|
const hasCoupon = item.couponAvailable === true || Number(item.couponAvailable) == 1
|
const discount = item.discountText != null ? item.discountText as string : ''
|
const hasConfiguredTags = item.benefitTags instanceof Array
|
const configuredTags: any[] = hasConfiguredTags ? item.benefitTags as any[] : []
|
const tags: string[] = []
|
for (let tagIndex = 0; tagIndex < configuredTags.length; tagIndex++) {
|
const tag = String(configuredTags[tagIndex]).trim()
|
if (tag != '' && tags.indexOf(tag) < 0) tags.push(tag)
|
}
|
if (!hasConfiguredTags) {
|
if (hasCoupon) tags.push('券')
|
if (discount != '' && discount != '券') tags.push(discount)
|
}
|
mapped.push({
|
id: '' + item.id,
|
name: item.merchantName != null ? item.merchantName as string : '人才权益商户',
|
merchantType: merchantType,
|
detailType: item.detailType != null ? item.detailType as string : 'MERCHANT',
|
detailBusinessId: item.detailBusinessId != null ? '' + item.detailBusinessId : '' + item.id,
|
targetAction: item.targetAction != null ? item.targetAction as string : 'MERCHANT',
|
hasCoupon: hasCoupon,
|
discount: discount,
|
tags: tags,
|
address: item.address != null ? item.address as string : '',
|
image: item.coverUrl != null && item.coverUrl != '' ? resolveApiUrl(item.coverUrl as string) : fallbackImage
|
})
|
}
|
return mapped
|
}
|
|
const loadInitialBenefits = () => {
|
initialLoading.value = true
|
initialError.value = false
|
loadMoreError.value = false
|
hasLoaded.value = false
|
currentPage.value = 0
|
if (isApiConfigured()) {
|
getMerchants(1, 4, '', talentId.value, (data: any) => {
|
const records: any[] = data != null && data.records instanceof Array ? data.records as any[] : []
|
visibleBenefits.value = mapMerchants(records)
|
loadedBenefitCount.value = records.length
|
currentPage.value = 1
|
hasMoreBenefits.value = data != null && data.total != null ? records.length < data.total : false
|
initialLoading.value = false
|
hasLoaded.value = true
|
}, () => {
|
initialLoading.value = false
|
initialError.value = true
|
})
|
return
|
}
|
setTimeout(() => {
|
if (failInitialOnce.value) {
|
failInitialOnce.value = false
|
initialLoading.value = false
|
initialError.value = true
|
return
|
}
|
initialLoading.value = false
|
hasLoaded.value = true
|
}, 450)
|
}
|
const retryInitialLoad = () => loadInitialBenefits()
|
const retryLoadMore = () => loadMoreBenefits()
|
|
const openSearch = () => uni.navigateTo({ url: '/pages/benefit-search/benefit-search' })
|
const handleScroll = (event: any) => {
|
const shouldShow = event.detail.scrollTop > 64
|
if (isSolidNav.value != shouldShow) isSolidNav.value = shouldShow
|
}
|
const chooseCategory = (name: string) => {
|
if (name == '臻享礼遇') {
|
uni.navigateTo({ url: '/pages/premium-benefits/premium-benefits' })
|
return
|
}
|
if (name == '暖心食享') {
|
uni.navigateTo({ url: '/pages/food-benefits/food-benefits' })
|
return
|
}
|
if (name == '悦览花城') {
|
uni.navigateTo({ url: '/pages/travel-benefits/travel-benefits' })
|
return
|
}
|
if (name == '舒心安居') {
|
uni.navigateTo({ url: '/pages/stay-benefits/stay-benefits' })
|
return
|
}
|
if (name == '怡乐生活') {
|
uni.navigateTo({ url: '/pages/entertainment-benefits/entertainment-benefits' })
|
return
|
}
|
if (name == '惠享好物') {
|
uni.navigateTo({ url: '/pages/shopping-benefits/shopping-benefits' })
|
return
|
}
|
if (name == '畅行无忧') {
|
uni.navigateTo({ url: '/pages/transport-benefits/transport-benefits' })
|
return
|
}
|
if (name == '全部') {
|
uni.navigateTo({ url: '/pages/benefit-search/benefit-search?mode=all' })
|
return
|
}
|
uni.showToast({ title: '已选择' + name, icon: 'none' })
|
}
|
const openMerchant = (item: any) => openMerchantDetail(item)
|
const loadMoreBenefits = () => {
|
if (initialLoading.value || loadingMore.value || !hasMoreBenefits.value) return
|
loadMoreError.value = false
|
loadingMore.value = true
|
if (isApiConfigured()) {
|
getMerchants(currentPage.value + 1, 4, '', talentId.value, (data: any) => {
|
const records: any[] = data != null && data.records instanceof Array ? data.records as any[] : []
|
const next = visibleBenefits.value.slice()
|
const mapped = mapMerchants(records)
|
for (let index = 0; index < mapped.length; index++) next.push(mapped[index])
|
visibleBenefits.value = next
|
loadedBenefitCount.value = next.length
|
currentPage.value = currentPage.value + 1
|
hasMoreBenefits.value = data != null && data.total != null ? next.length < data.total : false
|
loadingMore.value = false
|
}, () => {
|
loadingMore.value = false
|
loadMoreError.value = true
|
})
|
return
|
}
|
setTimeout(() => {
|
if (failLoadMoreOnce.value) {
|
failLoadMoreOnce.value = false
|
loadingMore.value = false
|
loadMoreError.value = true
|
return
|
}
|
const next = visibleBenefits.value.slice()
|
const end = Math.min(loadedBenefitCount.value + 4, allBenefits.length)
|
let index = loadedBenefitCount.value
|
while (index < end) {
|
next.push(allBenefits[index])
|
index++
|
}
|
visibleBenefits.value = next
|
loadedBenefitCount.value = end
|
hasMoreBenefits.value = end < allBenefits.length
|
loadingMore.value = false
|
}, 650)
|
}
|
const goHome = () => uni.reLaunch({ url: '/pages/index/index' })
|
const goCard = () => uni.reLaunch({ url: '/pages/card/card' })
|
const goPolicy = () => uni.reLaunch({ url: '/pages/policy/policy' })
|
const goProfile = () => uni.reLaunch({ url: '/pages/profile/profile' })
|
</script>
|
|
<style>
|
.page { height: 100%; background-color: #FFFFFF; }
|
.content { height: calc(100% - 126rpx); height: calc(100% - 126rpx - constant(safe-area-inset-bottom)); height: calc(100% - 126rpx - env(safe-area-inset-bottom)); background-color: #FFFFFF; }
|
.hero { height: 438rpx; position: relative; overflow: hidden; background-color: #F3A431; }
|
.hero-image { position: absolute; left: 0; top: 0; width: 100%; height: 438rpx; }
|
.hero-shade { position: absolute; left: 0; right: 0; top: 0; height: 236rpx; background-image: linear-gradient(180deg, rgba(235,129,5,.55) 0%, rgba(234,149,23,.18) 58%, rgba(234,149,23,0) 100%); }
|
.hero-bottom-fade { position: absolute; z-index: 2; left: 0; right: 0; bottom: 0; height: 150rpx; background-image: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.58) 62%, #FFFFFF 100%); }
|
.custom-nav { position: fixed; z-index: 30; left: 0; right: 0; top: 0; padding-bottom: 20rpx; }
|
.custom-nav-bg { position: absolute; z-index: 0; left: 0; right: 0; top: 0; bottom: 0; opacity: 0; background-image: linear-gradient(180deg, #F26A38 0%, #EE542B 100%); transition: opacity 280ms ease-out; }
|
.custom-nav.is-solid .custom-nav-bg { opacity: 1; }
|
.safe-space { position: relative; z-index: 2; height: 28px; }
|
.nav-row { position: relative; z-index: 2; height: 32px; box-sizing: border-box; padding-left: 36rpx; padding-right: 10px; flex-direction: row; align-items: center; justify-content: space-between; }
|
.page-title { color: #FFFFFF; font-size: 39rpx; font-weight: 700; text-shadow: 0 2rpx 8rpx rgba(100,61,12,.2); }
|
.capsule-space { width: 88px; height: 100%; flex-shrink: 0; }
|
.search-bar { position: absolute; z-index: 3; left: 34rpx; right: 34rpx; bottom: 118rpx; height: 82rpx; box-sizing: border-box; padding: 6rpx; border-radius: 42rpx; background-color: rgba(255,255,255,.97); border-width: 2rpx; border-style: solid; border-color: #FFE8DA; flex-direction: row; align-items: center; box-shadow: 0 4rpx 10rpx rgba(211,112,48,.06); }
|
.search-input-wrap { height: 70rpx; flex: 1; padding-left: 26rpx; flex-direction: row; align-items: center; }
|
.search-input-wrap image { width: 29rpx; height: 29rpx; margin-right: 16rpx; }
|
.search-input { height: 70rpx; flex: 1; color: #333333; font-size: 25rpx; }
|
.search-placeholder { color: #A4A4A4; font-size: 25rpx; }
|
.search-button { width: 142rpx; height: 70rpx; border-radius: 36rpx; background-image: linear-gradient(90deg, #FF9A32 0%, #FF6D21 100%); justify-content: center; align-items: center; }
|
.search-button text { color: #FFFFFF; font-size: 27rpx; }
|
|
.category-panel { position: relative; z-index: 4; margin: -86rpx 34rpx 0; padding: 36rpx 16rpx 24rpx; border-radius: 24rpx; background-color: rgba(255,255,255,.98); border-width: 2rpx; border-style: solid; border-color: #FFD8C0; flex-direction: row; flex-wrap: wrap; }
|
.category-item { width: 25%; height: 168rpx; align-items: center; justify-content: flex-start; }
|
.category-item image { width: 96rpx; height: 96rpx; }
|
.category-item text { margin-top: 7rpx; color: #3D3D3D; font-size: 23rpx; white-space: nowrap; }
|
|
.recommend-title { height: 100rpx; padding: 23rpx 35rpx 10rpx; flex-direction: row; align-items: center; }
|
.recommend-title image { width: 44rpx; height: 44rpx; margin-right: 12rpx; }
|
.recommend-title text { color: #333333; font-size: 31rpx; font-weight: 700; }
|
.benefit-list { padding: 0 31rpx; }
|
.benefit-item { height: 210rpx; margin-bottom: 23rpx; flex-direction: row; align-items: center; }
|
.merchant-image { width: 184rpx; height: 184rpx; border-radius: 17rpx; flex-shrink: 0; background-color: #EEEEEE; }
|
.benefit-info { height: 184rpx; flex: 1; min-width: 0; box-sizing: border-box; padding: 8rpx 0 7rpx 26rpx; justify-content: space-between; }
|
.benefit-name { color: #292929; font-size: 29rpx; line-height: 40rpx; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.tag-row { flex-direction: row; align-items: center; }
|
.tag-row text { margin-right: 11rpx; }
|
.coupon-tag { padding: 6rpx 8rpx; border-radius: 6rpx; background-color: #FFF0EC; color: #FF5D37; font-size: 21rpx; }
|
.discount-tag { padding: 6rpx 9rpx; border-radius: 6rpx; background-color: #FFF5E7; color: #FF7C2B; font-size: 21rpx; }
|
.address-row { display: flex; min-width: 0; box-sizing: border-box; padding: 7rpx 10rpx; border-radius: 7rpx; background-image: linear-gradient(90deg, rgba(239,240,242,.98) 0%, rgba(242,243,245,.76) 58%, rgba(255,255,255,0) 100%); flex-direction: row; align-items: center; }
|
.address-row image { width: 27rpx; height: 27rpx; margin-right: 9rpx; flex-shrink: 0; }
|
.address-row text { flex: 1; color: #7E8490; font-size: 21rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.benefit-empty { display: flex; height: 310rpx; justify-content: center; align-items: center; }
|
.benefit-empty text { color: #A5A8AD; font-size: 25rpx; }
|
.error-state { flex-direction: column; }
|
.error-state .retry-text { margin-top: 22rpx; color: #F26A38; }
|
.loading-row { display: flex; height: 90rpx; flex-direction: row; justify-content: center; align-items: center; }
|
.loading-spinner { width: 28rpx; height: 28rpx; margin-right: 13rpx; box-sizing: border-box; border-width: 3rpx; border-style: solid; border-color: #FFD6C4; border-top-color: #FF6C2B; border-radius: 50%; animation: list-loading 720ms linear infinite; }
|
.loading-row text { color: #989DA5; font-size: 22rpx; }
|
.load-error text { color: #F26A38; }
|
.no-more { display: flex; height: 90rpx; flex-direction: row; justify-content: center; align-items: center; }
|
.no-more view { width: 70rpx; height: 1rpx; background-color: #E7E7E7; }
|
.no-more text { margin: 0 18rpx; color: #B1B3B7; font-size: 21rpx; }
|
@keyframes list-loading { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
.content-bottom { height: 25rpx; }
|
|
.tab-bar { height: 126rpx; height: calc(126rpx + constant(safe-area-inset-bottom)); height: calc(126rpx + env(safe-area-inset-bottom)); padding: 8rpx 14rpx; padding-bottom: calc(8rpx + constant(safe-area-inset-bottom)); padding-bottom: calc(8rpx + env(safe-area-inset-bottom)); box-sizing: border-box; overflow: visible; background-color: #FFFFFF; border-top-width: 1rpx; border-top-color: #F0F0F0; flex-direction: row; justify-content: space-between; }
|
.tab { width: 20%; padding-top: 4rpx; align-items: center; justify-content: flex-start; color: #708099; font-size: 24rpx; }
|
.tab-icon-wrap { width: 66rpx; height: 66rpx; margin-bottom: 13rpx; border-radius: 33rpx; justify-content: center; align-items: center; }
|
.tab-icon-wrap image { width: 42rpx; height: 42rpx; }
|
.active-icon { background-color: #FFF1F1; }
|
.tab.active { color: #FF6030; font-weight: 700; }
|
.card-tab { margin-top: -16rpx; padding-top: 0; justify-content: flex-start; }
|
.card-icon { width: 160rpx; height: 160rpx; position: relative; justify-content: center; align-items: center; }
|
.card-icon image { width: 160rpx; height: 160rpx; }
|
.card-label { position: absolute; left: 0; right: 0; bottom: 42rpx; z-index: 1; color: #FFFFFF; font-size: 18rpx; font-weight: 700; text-align: center; }
|
</style>
|