Files
lgp-wx-plus/pages/package/index.vue
2026-08-20 19:15:41 +08:00

395 lines
9.4 KiB
Vue
Raw Permalink 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>
<view
class="container"
:class="['skin--' + pageSkin, 'density--' + pageDensity, 'list--' + packageList]"
:style="themePageStyle"
>
<ThemeNavbar title="套餐" :show-back="false" />
<view class="search-section" :style="{ background: themeSurface }">
<ThemeSearch
v-model="keyword"
placeholder="搜索套餐名称"
:variant="packageSearch"
@search="handleSearch"
/>
</view>
<view class="content-body">
<view class="page-head">
<text class="page-en" :style="{ color: themePrimary }">SETS</text>
<text class="page-cn" :style="{ color: themeText }">本期套餐</text>
<view class="page-rule" :style="{ background: themePrimary }"></view>
</view>
<view v-if="loading && items.length === 0" class="loading-container">
<ThemeSkeleton page="catalog" />
</view>
<view v-else-if="items.length === 0" class="empty-container">
<text class="empty-en" :style="{ color: themePrimary }">SETS</text>
<text class="empty-text" :style="{ color: themeTextSoft }">
{{ keyword ? '未找到相关套餐' : '还没有上架套餐' }}
</text>
</view>
<ThemeEnter v-else>
<view class="pkg-list" :class="'pkg-list--' + packageList">
<!-- pkg-card 放在本页节点上,避免 scoped 打不到 ThemeCardFrame 根节点 -->
<view
v-for="(item, index) in items"
:key="item.id"
class="pkg-card"
>
<ThemeCardFrame>
<view class="pkg-inner" @tap="goDetail(item.id)">
<image :src="item.cover" mode="aspectFill" class="pkg-cover" />
<view class="pkg-veil"></view>
<view class="pkg-meta">
<text class="pkg-vol" :style="{ color: themePrimary }">SET {{ padNo(index + 1) }}</text>
<text class="pkg-name">{{ item.name }}</text>
<text v-if="item.subtitle" class="pkg-sub">{{ item.subtitle }}</text>
<text class="pkg-count">{{ item.item_count || 0 }} 件搭配</text>
<view v-if="item.is_show_price" class="pkg-price">
<text class="pkg-origin">¥{{ item.original_amount_text }}</text>
<text class="pkg-quote" :style="{ color: themePrice }">¥{{ item.package_amount_text }}</text>
</view>
</view>
</view>
</ThemeCardFrame>
</view>
</view>
<view v-if="loadingMore" class="loading-more">
<ThemeSpinner text="加载更多" />
</view>
<view v-if="!hasMore && items.length > 0" class="no-more">
<text class="line" :style="{ background: themePrimary }"></text>
<text :style="{ color: themeTextSoft }">THE END</text>
<text class="line" :style="{ background: themePrimary }"></text>
</view>
</ThemeEnter>
</view>
<ThemeTabBar id="themeTabBar" ref="themeTabBar" />
</view>
</template>
<script>
/**
* 套餐 tab:图录式封面卡,有价权时划线原价 + 套餐价。
* 应用没开套餐时回首页,避免从微信 tab 列表点进空白页。
*/
import { getPackageListApi } from '@/api/page/package'
import { getThemeFeatures } from '@/utils/theme'
import themeMixin from '@/mixins/theme'
import ThemeNavbar from '@/components/theme/ThemeNavbar.vue'
import ThemeSearch from '@/components/theme/ThemeSearch.vue'
import ThemeSkeleton from '@/components/theme/ThemeSkeleton.vue'
import ThemeSpinner from '@/components/theme/ThemeSpinner.vue'
import ThemeEnter from '@/components/theme/ThemeEnter.vue'
import ThemeCardFrame from '@/components/theme/ThemeCardFrame.vue'
import ThemeTabBar from '@/components/theme/ThemeTabBar.vue'
export default {
components: {
ThemeNavbar,
ThemeSearch,
ThemeSkeleton,
ThemeSpinner,
ThemeEnter,
ThemeCardFrame,
ThemeTabBar,
},
mixins: [themeMixin],
data() {
return {
currentThemePage: 'package',
keyword: '',
items: [],
page: 1,
hasMore: true,
loading: false,
loadingMore: false,
}
},
async onShow() {
await this.applyThemeOnShow()
if (!getThemeFeatures().package_enabled) {
uni.switchTab({ url: '/pages/goods/index' })
return
}
this.reload()
},
onPullDownRefresh() {
this.reload().finally(() => {
uni.stopPullDownRefresh()
})
},
onReachBottom() {
this.loadMore()
},
methods: {
/** 检索套餐名/副标题,从第一页重拉 */
handleSearch() {
this.reload()
},
reload() {
this.page = 1
this.hasMore = true
this.items = []
return this.fetchList()
},
/**
* 拉套餐列表。拦截器已经拆过 result,这里只认 items / has_more。
*/
fetchList() {
if (this.loading || this.loadingMore || !this.hasMore) return Promise.resolve()
if (this.page === 1) {
this.loading = true
} else {
this.loadingMore = true
}
return getPackageListApi({ page: this.page, pageSize: 20, keyword: this.keyword })
.then((res) => {
const rows = (res && res.items) || []
this.items = this.page === 1 ? rows : this.items.concat(rows)
this.hasMore = !!(res && res.has_more)
this.page += 1
})
.catch(() => {
this.hasMore = false
})
.finally(() => {
this.loading = false
this.loadingMore = false
})
},
loadMore() {
this.fetchList()
},
goDetail(id) {
uni.navigateTo({ url: `/pages/package/detail?id=${id}` })
},
/** 图录编号:01 / 02,跟首页 VOL 同一套语气 */
padNo(n) {
return n < 10 ? '0' + n : String(n)
},
},
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.search-section {
padding: 20rpx 30rpx 8rpx;
position: sticky;
top: 0;
z-index: 10;
}
.content-body {
flex: 1;
}
.skin--magazine .page-en {
letter-spacing: 12rpx;
}
.skin--magazine .page-cn {
font-weight: 400;
letter-spacing: 8rpx;
}
.density--compact .pkg-list {
gap: 16rpx;
}
.density--airy .pkg-list {
gap: 40rpx;
}
.page-head {
padding: 12rpx 32rpx 8rpx;
}
.page-en {
display: block;
font-size: 20rpx;
letter-spacing: 8rpx;
}
.page-cn {
display: block;
margin-top: 6rpx;
font-size: 34rpx;
font-weight: 500;
letter-spacing: 4rpx;
}
.page-rule {
width: 48rpx;
height: 2rpx;
margin-top: 16rpx;
}
.loading-container,
.empty-container {
padding: 80rpx 0 160rpx;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
}
.empty-en {
font-size: 22rpx;
letter-spacing: 10rpx;
}
.empty-text {
margin-top: 16rpx;
font-size: 26rpx;
}
.pkg-list {
padding: 16rpx 24rpx 8rpx;
display: flex;
flex-direction: column;
gap: 28rpx;
}
.pkg-card {
display: block;
}
.pkg-inner {
position: relative;
overflow: hidden;
}
.pkg-cover {
width: 100%;
height: 420rpx;
display: block;
}
.pkg-veil {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 56%;
background: linear-gradient(transparent, rgba(28, 25, 23, 0.62));
pointer-events: none;
}
.pkg-meta {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 28rpx 28rpx 32rpx;
}
.pkg-vol {
display: block;
font-size: 18rpx;
letter-spacing: 6rpx;
}
.pkg-name,
.pkg-sub,
.pkg-count,
.pkg-origin {
color: #fff;
}
.pkg-name {
display: block;
margin-top: 8rpx;
font-size: 36rpx;
font-weight: 500;
letter-spacing: 2rpx;
}
.pkg-sub,
.pkg-count {
display: block;
margin-top: 8rpx;
font-size: 22rpx;
letter-spacing: 1rpx;
opacity: 0.86;
}
.pkg-price {
display: flex;
align-items: baseline;
gap: 14rpx;
margin-top: 14rpx;
}
.pkg-origin {
font-size: 22rpx;
text-decoration: line-through;
opacity: 0.7;
}
.pkg-quote {
font-size: 34rpx;
font-weight: 500;
letter-spacing: 1rpx;
}
/* 首图:第一张拉高,其余略矮,和装修「首图」对得上 */
.pkg-list--featured .pkg-card:first-child .pkg-cover {
height: 560rpx;
}
.pkg-list--featured .pkg-card:not(:first-child) .pkg-cover {
height: 320rpx;
}
/* 刊名条:左图右文,取消压字,换主题立刻能看出差别 */
.pkg-list--magazine .pkg-inner {
display: flex;
flex-direction: row;
align-items: stretch;
}
.pkg-list--magazine .pkg-cover {
width: 280rpx;
height: 220rpx;
flex-shrink: 0;
}
.pkg-list--magazine .pkg-veil {
display: none;
}
.pkg-list--magazine .pkg-meta {
position: relative;
padding: 24rpx 24rpx 24rpx 28rpx;
}
.pkg-list--magazine .pkg-name,
.pkg-list--magazine .pkg-sub,
.pkg-list--magazine .pkg-count,
.pkg-list--magazine .pkg-origin {
color: inherit;
}
/* 单列:更紧凑的左图右文 */
.pkg-list--list .pkg-inner {
display: flex;
flex-direction: row;
align-items: center;
}
.pkg-list--list .pkg-cover {
width: 220rpx;
height: 168rpx;
flex-shrink: 0;
}
.pkg-list--list .pkg-veil {
display: none;
}
.pkg-list--list .pkg-meta {
position: relative;
padding: 20rpx 24rpx;
}
.pkg-list--list .pkg-name,
.pkg-list--list .pkg-sub,
.pkg-list--list .pkg-count,
.pkg-list--list .pkg-origin {
color: inherit;
}
.pkg-list--list .pkg-name {
font-size: 30rpx;
}
.loading-more,
.no-more {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
padding: 28rpx 0 48rpx;
font-size: 20rpx;
letter-spacing: 6rpx;
}
.no-more .line {
width: 48rpx;
height: 1rpx;
opacity: 0.45;
}
</style>