| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- <script lang="ts" setup>
- import { usePagination, useRequest, actionDelegationMiddleware } from 'alova/client'
- import { storeToRefs } from 'pinia'
- import { reactive, ref, watch } from 'vue'
- import { getCategoryList, getNewCouponList } from '@/api/home'
- import CustomNavigationBar from '@/components/CustomNavigationBar.vue'
- import ExchangeCoupon from '@/components/exchangeCoupon.vue'
- import TemplateCoupon from "@/components/templateCoupon.vue";
- import MescrollUni from '@/components/mescroll.vue'
- import MescrollEmpty from '@/custom-components/custom-mescroll/components/mescroll-empty.vue'
- import { useAreaStore } from '@/store/area'
- import { getImageUrl } from '@/utils/imageUtil'
- import { safeAreaInsets } from '@/utils'
- import { useTokenStore } from '@/store/token'
- import { toLoginPage } from '@/utils/toLoginPage'
- const tokenStore = useTokenStore()
- const { hasLogin } = storeToRefs(tokenStore)
- definePage({
- style: {
- navigationBarTitleText: '优惠券',
- navigationStyle: 'custom',
- },
- })
- const areaStore = useAreaStore()
- const { currentCity } = storeToRefs(areaStore)
- const topSafeAreaHeight = safeAreaInsets?.top || 0
- const activeIndex = ref('0')
- const categoryType = ref('0');
- const couponType = ref('2')
- const searchValue = ref('')
- const showLoading = ref(false)
- const isSearching = ref(false)
- let mescroll = null
- const { send, data, pageCount, loading, page, pageSize, isLastPage, refresh, reload, onSuccess, onError } = usePagination((page, pageSize) => getNewCouponList({
- pageNo: page,
- pageSize,
- name: searchValue.value === '' ? '' : searchValue.value,
- relatedId: categoryType.value,
- type: couponType.value === '0' ? '' : couponType.value,
- country: currentCity.value?.value === 'all' ? '' : currentCity.value.value,
- }), {
- initialPage: 1,
- initialPageSize: 10,
- append: true,
- preloadNextPage: false,
- preloadPreviousPage: false,
- total: response => response?.total || 0,
- data: response => response?.records || [],
- immediate: false,
- middleware: actionDelegationMiddleware('GetCouponList')
- })
- const { send: sendCategoryList, data: categoryList, loading: categoryLoading } = useRequest(getCategoryList, {
- initialData: [
- {
- name: '按摩',
- value: '0',
- }
- ],
- immediate: false,
- })
- watch(() => data.value, (newVal) => {
- if (newVal.length === 0 && !loading.value) {
- mescroll.showEmpty()
- }
- else {
- mescroll.removeEmpty()
- }
- })
- // watch(() => searchValue.value, () => {
- // // 判断当前是否是搜索状态
- // if (searchValue.value !== '') {
- // isSearching.value = true
- // } else {
- // isSearching.value = false
- // }
- // })
- // ============ mescroll 配置 ============
- const downOption = reactive({
- use: true,
- auto: false,
- bgColor: '#f8f8fa',
- textColor: '#666',
- textInOffset: '下拉刷新',
- textOutOffset: '释放刷新',
- textLoading: '刷新中...',
- offset: 80,
- })
- const upOption = computed(() => ({
- use: true,
- auto: false,
- noMoreSize: 6,
- isBounce: true,
- page: {
- num: page.value,
- size: pageSize.value,
- },
- empty: {
- use: true,
- tip: '暂无优惠券',
- // btnText: '刷新试试',
- icon: '/static/images/mescroll-empty1.png',
- fixed: false,
- },
- textNoMore: '--- 已经到底了 ---',
- toTop: {
- src: '/static/images/mescroll-totop.png',
- offset: 1000,
- bottom: 120,
- },
- // 开启虚拟列表优化(大数据量时)
- virtual: {
- use: false,
- rowHeight: 300,
- }
- }))
- function mescrollInit(ref) {
- mescroll = ref
- }
- async function downCallback() {
- await refresh()
- }
- async function upCallback() {
- if (isLastPage.value) {
- console.log(mescroll)
- mescroll.endSuccess(0, false)
- return
- }
- page.value++
- }
- onSuccess(() => {
- let isNextPage = isLastPage.value
- // 此处处理alova isLastPage默认值为true,首次请求出现的无法加载下一页问题
- if (pageCount.value > page.value && isLastPage.value) {
- isNextPage = false
- }
- // mescroll.setPageNum(page.value - 1)
- mescroll.endSuccess(data.value.length, !isNextPage)
- })
- onError(() => {
- console.log('请求失败')
- mescroll.showEmpty()
- mescroll.endErr()
- })
- onLoad(async () => {
- await sendCategoryList()
- await send()
- })
- onHide(() => {
- areaStore.resetCurrentCity();
- })
- onShow(async () => {
- // 登录后重新获取分类列表
- if (hasLogin.value) {
- couponType.value = '2'
- // 重置分类类型
- categoryType.value = '0'
- searchValue.value = ''
- await sendCategoryList()
- }
- // 重新获取优惠券数据
- await send()
- })
- onUnload(() => {
- areaStore.resetCurrentCity();
- })
- // #ifdef MP-WEIXIN
- async function login() {
- const currentPage = getCurrentPages()[0]
- const redirectUrl = `/${currentPage.route}`
- toLoginPage({ queryString: `?redirect=${encodeURIComponent(redirectUrl)}` })
- }
- // #endif
- async function tabChange(item) {
- // 切换tab时,重置上拉加载
- // mescroll.resetUpScroll(true)
- // mescroll.showDownScroll()
- categoryType.value = item.value
- mescroll.endUpScroll(false)
- mescroll.scrollTo(0, 0)
- showLoading.value = true
- await reload()
- showLoading.value = false
- }
- async function search() {
- // if (activeIndex.value !== '0') {
- // activeIndex.value = '0'
- // }
- // categoryType.value = ''
- // if (couponType.value !== '0') {
- // couponType.value = '0'
- // }
- mescroll.endUpScroll(false)
- mescroll.scrollTo(0, 0)
- showLoading.value = true
- // 判断当前是否是搜索状态
- if (searchValue.value !== '') {
- isSearching.value = true
- } else {
- isSearching.value = false
- }
- await reload()
- showLoading.value = false
- }
- async function clear() {
- searchValue.value = ''
- mescroll.endUpScroll(false)
- mescroll.scrollTo(0, 0)
- showLoading.value = true
- await reload()
- isSearching.value = false
- showLoading.value = false
- }
- function searchChange(value) {
- searchValue.value = value
- }
- function regionSelection() {
- uni.navigateTo({
- url: '/pages-A/regionSelection/index',
- })
- }
- async function couponTypeChange(type) {
- couponType.value = type
- activeIndex.value = '0'
- mescroll.endUpScroll(false)
- mescroll.scrollTo(0, 0)
- showLoading.value = true
- await reload()
- showLoading.value = false
- }
- </script>
- <template>
- <view class="invite-container">
- <view class="invite-content">
- <!-- <view class="invite-content"
- :style="{ marginTop: `calc(${topSafeAreaHeight}px + 88rpx)`, height: `calc(100vh - ${topSafeAreaHeight}px - 108rpx)`}"> -->
- <!-- coupon-tabs -->
- <view
- class="coupon-types h-42rpx bg-#ffffff pr-10rpx pl-10rpx flex flex-row flex-nowrap justify-between items-center gap-34rpx">
- <view :class="{ 'coupon_type_active': couponType === '2' }"
- @click="couponTypeChange('2')">
- 折扣券
- </view>
- <view :class="{ 'coupon_type_active': couponType === '3' }"
- @click="couponTypeChange('3')">
- 满减券
- </view>
- <view :class="{ 'coupon_type_active': couponType === '4' }"
- @click="couponTypeChange('4')">
- 兑换券
- </view>
- </view>
- <!-- search -->
- <view class="invite-content-search bg-white">
- <view class="content-search-prefix" @tap="regionSelection">
- <up-icon size="12" style="margin-top: 2rpx;" name="map" />
- <text>
- {{ currentCity.name }}
- </text>
- </view>
- <up-input style="height: 100%; padding-left: 24rpx; font-size: 24rpx;" v-model="searchValue" border="none"
- class="invite-content-search-input" placeholder="请输入券名称" :clearable="true" @confirm="search"
- @clear="clear" />
- <view class="search-button">
- <up-button type="text" @tap="search">
- 搜索
- </up-button>
- </view>
- </view>
- <!-- category-tabs -->
- <view class="tabs-container flex">
- <!-- <view :class="[!isSearching ? 'expanded' : 'collapsed']" class="tabs-container flex"> -->
- <up-tabs v-model:current="activeIndex" :scrollable="true" class="tabs-content" lineWidth="0" :list="categoryList"
- :active-style="{ color: '#ffffff', backgroundColor: '#FF5365', borderRadius: '999rpx', padding: '0 16rpx', lineHeight: '44rpx', zIndex: 2 }"
- :inactive-style="{ backgroundColor: '#F5F5F5', borderRadius: '999rpx', padding: '0 16rpx', lineHeight: '44rpx' }"
- item-style="height: 44rpx; padding: 0 20rpx 0 0" style="width: 100%;" @change="tabChange" />
- <view class="tabs-mask"/>
- </view>
- <!-- coupon-list -->
- <view v-if="!hasLogin" class="empty-container">
- <view class="custom-empty"
- :style="{
- backgroundImage: `url(${getImageUrl('@img/index/login_bg.png')})`,
- backgroundSize: 'cover',
- backgroundPosition: 'center'
- }"/>
- <view class="login-btn" @click="login">去登录</view>
- </view>
- <view v-else class="invite-content-list">
- <!-- :style="{ height: `calc(100vh - ${topSafeAreaHeight}px - 70rpx - 88rpx - 20rpx - 176rpx)` }"> -->
- <mescroll-uni id="mescrollContainer" :down="downOption" :up="upOption" :fixed="false" offset="70"
- @init="mescrollInit" @down="downCallback" @up="upCallback" @emptyclick="reload">
- <template v-if="isSearching">
- <view v-for="(item, index) in data" :key="index">
- <!-- <view v-if="Array.isArray(item.data)" class="text-34rpx text-#333333 border">{{ item.name }}
- </view> -->
- <view v-for="coupon in item.data" :key="item.templateId" class="invite-content-item">
- <!-- <invite-item :data="item" /> -->
- <template v-if="coupon.type === '1'">
- <ExchangeCoupon :coupon="coupon" />
- </template>
- <template v-else>
- <TemplateCoupon :coupon="coupon" />
- </template>
- </view>
- </view>
- </template>
- <template v-else>
- <view v-for="item in data" :key="item.templateId" class="invite-content-item">
- <!-- <invite-item :data="item" /> -->
- <!-- 优惠券类型:4-兑换券,2-折扣券,3-满减券 -->
- <template v-if="item.type == '4'">
- <ExchangeCoupon :coupon="item" />
- </template>
- <template v-else>
- <TemplateCoupon :coupon="item" />
- </template>
- </view>
- </template>
- <!-- 滚动区域内的loading遮罩 -->
- <view v-if="showLoading && loading" class="loading-mask">
- <view class="loading-spinner" />
- <text class="loading-text">加载中...</text>
- </view>
- </mescroll-uni>
- </view>
- </view>
- </view>
- </template>
- <style lang="scss" scoped>
- .invite-container {
- padding: 0 20rpx 20rpx;
- margin-bottom: 20rpx;
- background: linear-gradient( 180deg, #FF8180 0%, rgba(255,127,92,0.49) 30%);
- border-radius: 0 0 32rpx 32rpx;
- .invite-content {
- // max-height: 1130rpx;
- display: flex;
- flex-direction: column;
- border-radius: 32rpx 32rpx 32rpx 32rpx;
- background-color: #ffffff;
- padding: 24rpx 20rpx 20rpx;
- .coupon-types {
- view {
- width: 94rpx;
- line-height: 42rpx;
- text-align: center;
- font-weight: 400;
- font-size: 30rpx;
- color: rgba(0,0,0,0.5);
- position: relative;
- }
- .coupon_type_active {
- font-weight: 600 !important;
- font-size: 30rpx;
- color: #333333;
- z-index: 1;
- // 添加伪元素作为下划线
- &:after {
- content: '';
- position: absolute;
- bottom: 6rpx;
- left: 0;
- width: 100%;
- height: 10rpx;
- background-color: #FF5666;
- z-index: -1;
- }
- }
- }
- .invite-content-search {
- padding: 0 8rpx 0 24rpx;
- margin: 31rpx 0 24rpx;
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- align-items: center;
- height: 64rpx;
- background: #F0F0F0;
- border-radius: 999rpx;
- .content-search-prefix {
- min-width: 127rpx;
- // border-top-left-radius: 10rpx;
- // border-bottom-left-radius: 10rpx;
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- text {
- font-weight: 400;
- font-size: 26rpx;
- color: #333333;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- max-width: 200rpx;
- margin: 0 23rpx 0 1rpx;
- }
- &:after {
- content: '';
- position: absolute;
- right: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 1rpx;
- height: 20rpx;
- background: #333333;
- }
- }
- .invite-content-search-input {
- height: 60rpx;
- border-radius: 0;
- }
- .search-button {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100rpx;
- height: 44rpx;
- background: #FF5365;
- border-radius: 999rpx 999rpx 999rpx 999rpx;
- font-weight: 400;
- font-size: 26rpx;
- color: #FFFFFF;
- }
- }
- .tabs-container {
- height: 46rpx;
- display: flex;
- flex-direction: row;
- justify-content: start;
- align-items: center;
- position: relative;
- margin-bottom: 20rpx;
- overflow-x: auto; /* 添加横向滚动 */
- white-space: nowrap; /* 防止标签换行 */
- .tabs-content {
- line-height: 46rpx;
- font-weight: 400;
- font-size: 26rpx;
- color: #333333;
- }
- .tabs-mask {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 1;
- width: 159rpx;
- height: 44rpx;
- background: linear-gradient(135deg,rgba(255, 85, 101, 0) 0%, #FFFFFF 100%, );
- pointer-events: none;
- }
- }
- /* 收起状态 */
- .tabs-container.collapsed {
- opacity: 0;
- max-height: 0;
- }
- /* 展开状态 */
- .tabs-container.expanded {
- opacity: 1;
- max-height: 48rpx;
- }
- .invite-content-list {
- display: flex;
- flex-direction: column;
- position: relative;
- #mescrollContainer {
- height: 100%;
- }
- .invite-content-item {
- margin-bottom: 20rpx;
- }
- // loading遮罩样式
- .loading-mask {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(255, 255, 255, 0.9);
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- z-index: 999;
- }
- .loading-spinner {
- width: 40rpx;
- height: 40rpx;
- border: 4rpx solid #e0e0e0;
- border-top-color: #ed6b66;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- .loading-text {
- margin-top: 16rpx;
- font-size: 24rpx;
- color: #666;
- }
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- }
- }
- .accordion-enter-active,
- .accordion-leave-active {
- transition: all 0.4s ease;
- overflow: hidden;
- }
- .accordion-enter-from,
- .accordion-leave-to {
- max-height: 0;
- opacity: 0;
- transform: translateY(-10rpx);
- }
- .accordion-enter-to,
- .accordion-leave-from {
- max-height: 88rpx;
- opacity: 1;
- transform: translateY(0);
- }
- }
- /* 自定义 up-empty 图标大小 */
- .empty-container {
- width: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 13rpx;
- margin: 20rpx 0 35rpx;
- .custom-empty {
- width: 160rpx;
- height: 140rpx;
- }
- .login-btn {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 102rpx;
- height: 44rpx;
- background: #FF5365;
- border-radius: 999rpx;
- font-weight: 400;
- font-size: 26rpx;
- color: #FFFFFF;
- }
- }
- ::v-deep {
- .uni-input-placeholder {
- font-weight: 400;
- font-size: 24rpx !important;
- color: #808080;
- }
- .mescroll-empty {
- padding-top: 150rpx;
- }
- }
- </style>
|