Merge branch 'feat/appointment-goods'
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view :style="themeStyle" class="content">
|
||||
<view class="portrait-box">
|
||||
<image src="/static/pointTrade/point_bg_1.png" mode=""></image>
|
||||
<image class="point-img" src="/static/pointTrade/tradehall.png" />
|
||||
<image src="/pages/mine/static/point_bg_1.png" mode=""></image>
|
||||
<image class="point-img" src="/pages/mine/static/tradehall.png" />
|
||||
<view class="position-point">
|
||||
|
||||
|
||||
|
||||
BIN
pages/mine/static/point_bg_1.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
pages/mine/static/tradehall.png
Normal file
|
After Width: | Height: | Size: 742 B |
@@ -35,7 +35,7 @@
|
||||
<view class="select-view">
|
||||
<view class="select-cell" v-if="applyInfo.returnGoods" @click="onSelect(1)">
|
||||
<view class="select-image">
|
||||
<image class="select-icon" src="/static/order/t1.png"></image>
|
||||
<image class="select-icon" src="/pages/order/static/t1.png"></image>
|
||||
</view>
|
||||
<view class="cell-body">
|
||||
<view class="select-title">退货</view>
|
||||
@@ -45,7 +45,7 @@
|
||||
</view>
|
||||
<view class="select-cell" v-if="applyInfo.returnMoney" @click="onSelect(3)">
|
||||
<view class="select-image">
|
||||
<image class="select-icon" src="/static/order/t3.png"></image>
|
||||
<image class="select-icon" src="/pages/order/static/t3.png"></image>
|
||||
</view>
|
||||
<view class="cell-body">
|
||||
<view class="select-title">退款</view>
|
||||
|
||||
BIN
pages/order/static/t1.png
Normal file
|
After Width: | Height: | Size: 952 B |
BIN
pages/order/static/t3.png
Normal file
|
After Width: | Height: | Size: 617 B |
@@ -113,7 +113,7 @@
|
||||
<u-icon v-if="item.title != 'APPLE'" color="#fff" size="42" :name="item.icon"
|
||||
@click="navigateLogin(item)">
|
||||
</u-icon>
|
||||
<u-image v-else src="/static/appleidButton@2x.png" :lazy-load="false" @click="navigateLogin(item)"
|
||||
<u-image v-else src="/pages/passport/static/appleidButton@2x.png" :lazy-load="false" @click="navigateLogin(item)"
|
||||
width="80" height="80" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
BIN
pages/passport/static/appleidButton@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 44 KiB |
@@ -303,7 +303,7 @@ page {
|
||||
.star-item {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: url("/static/star.png");
|
||||
background: url("/pages/product/static/star.png");
|
||||
background-size: 100%;
|
||||
opacity: 0.25;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.group-wrapper {
|
||||
background: url("/static/exchange.png");
|
||||
background: url("/pages/product/static/exchange.png");
|
||||
background-size: cover;
|
||||
}
|
||||
.u-group-row {
|
||||
|
||||
BIN
pages/product/static/exchange.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
pages/product/static/star.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -27,7 +27,7 @@
|
||||
</u-navbar>
|
||||
<!-- 顶部栏 -->
|
||||
<view class="header-wraper">
|
||||
<image class="header-banner" mode="aspectFit" src="/static/join-buy.png"></image>
|
||||
<image class="header-banner" mode="aspectFit" src="/pages/promotion/static/join-buy.png"></image>
|
||||
</view>
|
||||
<!-- 商品栏 -->
|
||||
<div class="swiper">
|
||||
@@ -156,7 +156,7 @@ function GET_AssembleGoods() {
|
||||
}
|
||||
|
||||
.header-wraper {
|
||||
background: url('/static/bg.png') no-repeat center center;
|
||||
background: url('/pages/promotion/static/bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
|
||||
@@ -32,7 +32,7 @@ async function init() {
|
||||
padding: 0 20rpx;
|
||||
height: 300rpx;
|
||||
flex-shrink: 0;
|
||||
background: url("/static/point-bg.png") no-repeat;
|
||||
background: url("/pages/promotion/static/point-bg.png") no-repeat;
|
||||
background-size: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
@left-click="back"
|
||||
></u-navbar>
|
||||
<view class="header-wraper">
|
||||
<image class="header-banner" mode="aspectFit" src="/static/seckill.png"></image>
|
||||
<image class="header-banner" mode="aspectFit" src="/pages/promotion/static/seckill.png"></image>
|
||||
</view>
|
||||
<scroll-view scroll-x>
|
||||
<view class="index-navs">
|
||||
@@ -176,7 +176,7 @@ function clickNavigateTime(type: number) {
|
||||
}
|
||||
|
||||
.header-wraper {
|
||||
background: url('/static/bg.png') no-repeat center center;
|
||||
background: url('/pages/promotion/static/bg.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
|
||||
BIN
pages/promotion/static/bg.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 31 KiB |
BIN
pages/promotion/static/join-buy.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 25 KiB |
BIN
pages/promotion/static/point-bg.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
pages/promotion/static/seckill.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,208 +1,262 @@
|
||||
<template>
|
||||
<view class="home-page" :style="themeStyle">
|
||||
<view
|
||||
v-for="(item, index) in pageData.list"
|
||||
:key="`${item.type}-${index}`"
|
||||
class="floor-block"
|
||||
>
|
||||
<u-navbar
|
||||
class="navbar"
|
||||
v-if="item.type == 'search'"
|
||||
:auto-back="false"
|
||||
:fixed="index === 1 ? false : true"
|
||||
:placeholder="index === 1 ? false : true"
|
||||
left-icon=""
|
||||
title=""
|
||||
bg-color="#ffffff"
|
||||
>
|
||||
<template #left>
|
||||
<view class="navbar-search-wrap" :style="searchBarStyle" @click.stop>
|
||||
<search :res="item.options" />
|
||||
</view>
|
||||
</template>
|
||||
</u-navbar>
|
||||
<carousel v-if="item.type == 'carousel'" :res="item.options" />
|
||||
<titleLayout v-if="item.type == 'title'" :res="item.options" />
|
||||
<leftOneRightTwo v-if="item.type == 'leftOneRightTwo'" :res="item.options" />
|
||||
<leftTwoRightOne v-if="item.type == 'leftTwoRightOne'" :res="item.options" />
|
||||
<topOneBottomTwo v-if="item.type == 'topOneBottomTwo'" :res="item.options" />
|
||||
<topTwoBottomOne v-if="item.type == 'topTwoBottomOne'" :res="item.options" />
|
||||
<flexThree v-if="item.type == 'flexThree'" :res="item.options" />
|
||||
<flexFive v-if="item.type == 'flexFive'" :res="item.options" />
|
||||
<flexFour v-if="item.type == 'flexFour'" :res="item.options" />
|
||||
<flexTwo v-if="item.type == 'flexTwo'" :res="item.options" />
|
||||
<textPicture v-if="item.type == 'textPicture'" :res="item.options" />
|
||||
<menuLayout v-if="item.type == 'menu'" :res="item.options" />
|
||||
<flexOne v-if="item.type == 'flexOne'" :res="item.options" />
|
||||
<goods
|
||||
v-if="item.type == 'goods'"
|
||||
:enableBottomLoad="enableLoad"
|
||||
:res="item.options"
|
||||
/>
|
||||
<group v-if="item.type == 'group'" :res="item.options" />
|
||||
<notice v-if="item.type == 'notice'" :res="item.options" />
|
||||
<promotions v-if="item.type == 'promotionDetail'" :res="item.options" />
|
||||
<videoLayout v-if="item.type == 'video'" :res="item.options" />
|
||||
</view>
|
||||
<fetchCoupon ref="couponRef" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import {
|
||||
onShow,
|
||||
onReady,
|
||||
onReachBottom,
|
||||
onPullDownRefresh,
|
||||
} from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import carousel from '@/pages/tabbar/home/template/tpl_banner.vue'
|
||||
import titleLayout from '@/pages/tabbar/home/template/tpl_title.vue'
|
||||
import leftOneRightTwo from '@/pages/tabbar/home/template/tpl_left_one_right_two.vue'
|
||||
import leftTwoRightOne from '@/pages/tabbar/home/template/tpl_left_two_right_one.vue'
|
||||
import topOneBottomTwo from '@/pages/tabbar/home/template/tpl_top_one_bottom_two.vue'
|
||||
import topTwoBottomOne from '@/pages/tabbar/home/template/tpl_top_two_bottom_one.vue'
|
||||
import flexOne from '@/pages/tabbar/home/template/tpl_flex_one.vue'
|
||||
import flexTwo from '@/pages/tabbar/home/template/tpl_flex_two.vue'
|
||||
import flexThree from '@/pages/tabbar/home/template/tpl_flex_three.vue'
|
||||
import flexFive from '@/pages/tabbar/home/template/tpl_flex_five.vue'
|
||||
import flexFour from '@/pages/tabbar/home/template/tpl_flex_four.vue'
|
||||
import textPicture from '@/pages/tabbar/home/template/tpl_text_picture.vue'
|
||||
import menuLayout from '@/pages/tabbar/home/template/tpl_menu.vue'
|
||||
import search from '@/pages/tabbar/home/template/tpl_search.vue'
|
||||
import group from '@/pages/tabbar/home/template/tpl_group.vue'
|
||||
import videoLayout from '@/pages/tabbar/home/template/tpl_video.vue'
|
||||
import goods from '@/pages/tabbar/home/template/tpl_goods.vue'
|
||||
import notice from '@/pages/tabbar/home/template/tpl_notice.vue'
|
||||
import promotions from '@/pages/tabbar/home/template/tpl_promotions_detail.vue'
|
||||
import fetchCoupon from '@/pages/tabbar/home/template/fetch_coupon.vue'
|
||||
import { getFloorData } from '@/api/home'
|
||||
|
||||
const HOME_LAYOUT_PADDING_RPX = 16
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const couponRef = ref<InstanceType<typeof fetchCoupon>>()
|
||||
const pageData = ref<{ list: any[] }>({ list: [] })
|
||||
const enableLoad = ref(false)
|
||||
const loading = ref(false)
|
||||
const searchBarWidth = ref(0)
|
||||
|
||||
const searchBarStyle = computed(() => {
|
||||
if (searchBarWidth.value) {
|
||||
return { width: `${searchBarWidth.value}px` }
|
||||
}
|
||||
return { width: '100%' }
|
||||
})
|
||||
|
||||
function setCapsuleInset() {
|
||||
try {
|
||||
const { windowWidth } = uni.getWindowInfo()
|
||||
const leftPadding = uni.upx2px(HOME_LAYOUT_PADDING_RPX)
|
||||
const rightPadding = uni.upx2px(HOME_LAYOUT_PADDING_RPX)
|
||||
// #ifdef MP-WEIXIN
|
||||
const menuButton = uni.getMenuButtonBoundingClientRect()
|
||||
searchBarWidth.value = windowWidth - (windowWidth - menuButton.left) - leftPadding
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
searchBarWidth.value = windowWidth - leftPadding - rightPadding
|
||||
// #endif
|
||||
} catch (e) {
|
||||
searchBarWidth.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
function fetchCouponFn() {
|
||||
couponRef.value?.firstGetAuto()
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
getFloorData()
|
||||
.then((res) => {
|
||||
const payload = res?.data != null ? res.data : res
|
||||
const pageDataStr = payload?.result?.pageData
|
||||
const isSuccess = payload?.success === true || payload?.code === 200
|
||||
if (!isSuccess || !pageDataStr) return
|
||||
|
||||
try {
|
||||
const result = JSON.parse(pageDataStr)
|
||||
pageData.value = Array.isArray(result?.list) ? result : { list: [] }
|
||||
const lastItem = pageData.value.list[pageData.value.list.length - 1]
|
||||
enableLoad.value =
|
||||
lastItem?.type === 'goods' || lastItem?.model === 'goods'
|
||||
} catch (parseError) {
|
||||
console.error('parse pageData failed', parseError)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('getFloorData failed', err)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onReady(() => {
|
||||
setCapsuleInset()
|
||||
init()
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.showShareMenu({ withShareTicket: true })
|
||||
// #endif
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
setTimeout(() => {
|
||||
fetchCouponFn()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
uni.$emit('onReachBottom', true)
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
init()
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/tabbar/home/template/tpl.scss';
|
||||
|
||||
.home-page {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.floor-block {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
width: 100%;
|
||||
|
||||
:deep(.u-navbar__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.u-navbar__content__left) {
|
||||
flex: 1;
|
||||
width: auto !important;
|
||||
max-width: 100%;
|
||||
padding: 0 0 0 $home-layout-padding !important;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-search-wrap {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="home-page" :style="[themeStyle, pageBgStyle]">
|
||||
<view
|
||||
v-for="(item, index) in pageData.list"
|
||||
:key="`${item.type}-${index}`"
|
||||
class="floor-block"
|
||||
>
|
||||
<u-navbar
|
||||
class="navbar"
|
||||
v-if="item.type == 'search'"
|
||||
:auto-back="false"
|
||||
:fixed="index === 1 ? false : true"
|
||||
:placeholder="index === 1 ? false : true"
|
||||
left-icon=""
|
||||
title=""
|
||||
:bg-color="topColor"
|
||||
>
|
||||
<template #left>
|
||||
<view class="navbar-search-wrap" :style="searchBarStyle" @click.stop>
|
||||
<search :res="item.options" />
|
||||
</view>
|
||||
</template>
|
||||
</u-navbar>
|
||||
<homeComb v-if="item.type == 'homeComb'" :res="item.options" />
|
||||
<carousel v-if="item.type == 'carousel'" :res="item.options" />
|
||||
<titleLayout v-if="item.type == 'title'" :res="item.options" />
|
||||
<leftOneRightTwo v-if="item.type == 'leftOneRightTwo'" :res="item.options" />
|
||||
<leftTwoRightOne v-if="item.type == 'leftTwoRightOne'" :res="item.options" />
|
||||
<topOneBottomTwo v-if="item.type == 'topOneBottomTwo'" :res="item.options" />
|
||||
<topTwoBottomOne v-if="item.type == 'topTwoBottomOne'" :res="item.options" />
|
||||
<flexThree v-if="item.type == 'flexThree'" :res="item.options" />
|
||||
<flexFive v-if="item.type == 'flexFive'" :res="item.options" />
|
||||
<flexFour v-if="item.type == 'flexFour'" :res="item.options" />
|
||||
<flexTwo v-if="item.type == 'flexTwo'" :res="item.options" />
|
||||
<textPicture v-if="item.type == 'textPicture'" :res="item.options" />
|
||||
<menuLayout v-if="item.type == 'menu'" :res="item.options" />
|
||||
<flexOne v-if="item.type == 'flexOne'" :res="item.options" />
|
||||
<goods
|
||||
v-if="item.type == 'goods'"
|
||||
:enableBottomLoad="enableLoad"
|
||||
:res="item.options"
|
||||
/>
|
||||
<group v-if="item.type == 'group'" :res="item.options" />
|
||||
<notice v-if="item.type == 'notice'" :res="item.options" />
|
||||
<promotions v-if="item.type == 'promotionDetail'" :res="item.options" />
|
||||
<videoLayout v-if="item.type == 'video'" :res="item.options" />
|
||||
<blank v-if="item.type == 'blank'" :res="item.options" />
|
||||
<lineLayout v-if="item.type == 'line'" :res="item.options" />
|
||||
<richText v-if="item.type == 'richText'" :res="item.options" />
|
||||
<integral v-if="item.type == 'integral'" :res="item.options" />
|
||||
<coupon v-if="item.type == 'coupon'" :res="item.options" />
|
||||
<articleList v-if="item.type == 'article'" :res="item.options" />
|
||||
<shopList v-if="item.type == 'shop'" :res="item.options" />
|
||||
</view>
|
||||
<fetchCoupon ref="couponRef" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import {
|
||||
onShow,
|
||||
onReady,
|
||||
onReachBottom,
|
||||
onPullDownRefresh,
|
||||
} from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import carousel from '@/pages/tabbar/home/template/tpl_banner.vue'
|
||||
import titleLayout from '@/pages/tabbar/home/template/tpl_title.vue'
|
||||
import leftOneRightTwo from '@/pages/tabbar/home/template/tpl_left_one_right_two.vue'
|
||||
import leftTwoRightOne from '@/pages/tabbar/home/template/tpl_left_two_right_one.vue'
|
||||
import topOneBottomTwo from '@/pages/tabbar/home/template/tpl_top_one_bottom_two.vue'
|
||||
import topTwoBottomOne from '@/pages/tabbar/home/template/tpl_top_two_bottom_one.vue'
|
||||
import flexOne from '@/pages/tabbar/home/template/tpl_flex_one.vue'
|
||||
import flexTwo from '@/pages/tabbar/home/template/tpl_flex_two.vue'
|
||||
import flexThree from '@/pages/tabbar/home/template/tpl_flex_three.vue'
|
||||
import flexFive from '@/pages/tabbar/home/template/tpl_flex_five.vue'
|
||||
import flexFour from '@/pages/tabbar/home/template/tpl_flex_four.vue'
|
||||
import textPicture from '@/pages/tabbar/home/template/tpl_text_picture.vue'
|
||||
import menuLayout from '@/pages/tabbar/home/template/tpl_menu.vue'
|
||||
import search from '@/pages/tabbar/home/template/tpl_search.vue'
|
||||
import group from '@/pages/tabbar/home/template/tpl_group.vue'
|
||||
import videoLayout from '@/pages/tabbar/home/template/tpl_video.vue'
|
||||
import goods from '@/pages/tabbar/home/template/tpl_goods.vue'
|
||||
import notice from '@/pages/tabbar/home/template/tpl_notice.vue'
|
||||
import promotions from '@/pages/tabbar/home/template/tpl_promotions_detail.vue'
|
||||
import fetchCoupon from '@/pages/tabbar/home/template/fetch_coupon.vue'
|
||||
import blank from '@/pages/tabbar/home/template/tpl_blank.vue'
|
||||
import lineLayout from '@/pages/tabbar/home/template/tpl_line.vue'
|
||||
import richText from '@/pages/tabbar/home/template/tpl_rich_text.vue'
|
||||
import integral from '@/pages/tabbar/home/template/tpl_integral.vue'
|
||||
import coupon from '@/pages/tabbar/home/template/tpl_coupon.vue'
|
||||
import articleList from '@/pages/tabbar/home/template/tpl_article.vue'
|
||||
import shopList from '@/pages/tabbar/home/template/tpl_shop.vue'
|
||||
import homeComb from '@/pages/tabbar/home/template/tpl_home_comb.vue'
|
||||
import { getFloorData } from '@/api/home'
|
||||
|
||||
const HOME_LAYOUT_PADDING_RPX = 16
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const couponRef = ref<InstanceType<typeof fetchCoupon>>()
|
||||
const pageData = ref<Record<string, any>>({ list: [] })
|
||||
const enableLoad = ref(false)
|
||||
const loading = ref(false)
|
||||
const searchBarWidth = ref(0)
|
||||
|
||||
const topColor = computed(() => pageData.value?.topColor || '#ffffff')
|
||||
|
||||
// 页面设置-样式:画布背景(背景色 / 背景图 + 填充方式)
|
||||
const pageBgStyle = computed(() => {
|
||||
const data: any = pageData.value
|
||||
if (data?.bgType === 'image' && data?.bgImage) {
|
||||
const size =
|
||||
data.bgFill === 'contain'
|
||||
? 'contain'
|
||||
: data.bgFill === 'cover'
|
||||
? 'cover'
|
||||
: '100% 100%'
|
||||
return {
|
||||
backgroundImage: `url(${data.bgImage})`,
|
||||
backgroundSize: size,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundColor: data.bgColor || '#f9f9f9',
|
||||
}
|
||||
}
|
||||
if (data?.bgColor) {
|
||||
return { backgroundColor: data.bgColor }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const searchBarStyle = computed(() => {
|
||||
if (searchBarWidth.value) {
|
||||
return { width: `${searchBarWidth.value}px` }
|
||||
}
|
||||
return { width: '100%' }
|
||||
})
|
||||
|
||||
function setCapsuleInset() {
|
||||
try {
|
||||
const { windowWidth } = uni.getWindowInfo()
|
||||
const leftPadding = uni.upx2px(HOME_LAYOUT_PADDING_RPX)
|
||||
const rightPadding = uni.upx2px(HOME_LAYOUT_PADDING_RPX)
|
||||
// #ifdef MP-WEIXIN
|
||||
const menuButton = uni.getMenuButtonBoundingClientRect()
|
||||
searchBarWidth.value = windowWidth - (windowWidth - menuButton.left) - leftPadding
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
searchBarWidth.value = windowWidth - leftPadding - rightPadding
|
||||
// #endif
|
||||
} catch (e) {
|
||||
searchBarWidth.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
function fetchCouponFn() {
|
||||
couponRef.value?.firstGetAuto()
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
getFloorData()
|
||||
.then((res) => {
|
||||
const payload = res?.data != null ? res.data : res
|
||||
const pageDataStr = payload?.result?.pageData
|
||||
const isSuccess = payload?.success === true || payload?.code === 200
|
||||
if (!isSuccess || !pageDataStr) return
|
||||
|
||||
try {
|
||||
const result = JSON.parse(pageDataStr)
|
||||
pageData.value = Array.isArray(result?.list)
|
||||
? { ...result, list: result.list.filter((i: any) => !i?.hidden) }
|
||||
: { list: [] }
|
||||
// 页面设置-内容:页面标题同步到导航栏
|
||||
if (result?.title) {
|
||||
uni.setNavigationBarTitle({ title: result.title })
|
||||
}
|
||||
// 页面设置-样式:顶部颜色与标题颜色同步到导航栏
|
||||
if (result?.topColor || result?.titleColor) {
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: result?.titleColor === 'white' ? '#ffffff' : '#000000',
|
||||
backgroundColor: result?.topColor || '#ffffff',
|
||||
})
|
||||
}
|
||||
const lastItem = pageData.value.list[pageData.value.list.length - 1]
|
||||
enableLoad.value =
|
||||
lastItem?.type === 'goods' || lastItem?.model === 'goods'
|
||||
} catch (parseError) {
|
||||
console.error('parse pageData failed', parseError)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('getFloorData failed', err)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onReady(() => {
|
||||
setCapsuleInset()
|
||||
init()
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.showShareMenu({ withShareTicket: true })
|
||||
// #endif
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
setTimeout(() => {
|
||||
fetchCouponFn()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
uni.$emit('onReachBottom', true)
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
init()
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/tabbar/home/template/tpl.scss';
|
||||
|
||||
.home-page {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.floor-block {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
width: 100%;
|
||||
|
||||
:deep(.u-navbar__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.u-navbar__content__left) {
|
||||
flex: 1;
|
||||
width: auto !important;
|
||||
max-width: 100%;
|
||||
padding: 0 0 0 $home-layout-padding !important;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-search-wrap {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<view style="width: 120rpx; height: 120rpx; float: right">
|
||||
<image
|
||||
@click="enableShowCoupon = false"
|
||||
src="/static/cpauto1.png"
|
||||
src="https://lilishop-uniapp.oss-cn-beijing.aliyuncs.com/cpauto1.png"
|
||||
style="width: 100%; height: 100%"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
@@ -243,3 +243,35 @@ function seacnCode() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 广告魔方各布局共用的样式字段(装修面板配置;老数据无字段时返回空对象,保持原样) */
|
||||
export function cubeWrapperStyle(res) {
|
||||
if (!res || res.paddingTop === undefined) return {};
|
||||
const r = (v) => `${Number(v) || 0}rpx`;
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
return {
|
||||
padding: `${r(res.paddingTop)} ${r(res.paddingX)} ${r(res.paddingBottom)}`,
|
||||
background: bg,
|
||||
borderRadius: r(res.borderRadius),
|
||||
marginBottom: r(res.pageGap),
|
||||
overflow: Number(res.borderRadius) > 0 ? "hidden" : "visible",
|
||||
};
|
||||
}
|
||||
|
||||
/** 内容圆角(作用于单张图片容器) */
|
||||
export function cubeImgStyle(res) {
|
||||
if (!res || res.imgRadius === undefined) return {};
|
||||
return {
|
||||
borderRadius: `${Number(res.imgRadius) || 0}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
}
|
||||
|
||||
/** 内容间距(作用于图片布局容器,flex gap) */
|
||||
export function cubeGapStyle(res) {
|
||||
if (!res || res.contentGap === undefined) return {};
|
||||
return { gap: `${Number(res.contentGap) || 0}rpx` };
|
||||
}
|
||||
|
||||
104
pages/tabbar/home/template/tpl_article.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<view class="tpl-article" :class="{ styled: styled }" :style="wrapperStyle" v-if="articleList.length">
|
||||
<view class="article-title" v-if="title">{{ title }}</view>
|
||||
<view
|
||||
class="article-item"
|
||||
v-for="item in articleList"
|
||||
:key="item.id"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<view class="article-text">
|
||||
<view class="article-name" :style="nameStyle">{{ item.title }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { getArticleList } from "@/api/article.js";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const articleList = ref<any[]>([]);
|
||||
|
||||
const title = computed(() => props.res?.list?.[0]?.title || "");
|
||||
const count = computed(() => props.res?.list?.[0]?.count || 3);
|
||||
const categoryId = computed(() => props.res?.list?.[0]?.categoryId || "");
|
||||
|
||||
// 面板保存过样式字段才启用自定义样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (styled.value) {
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
const radius = Number(res.borderRadius) || 0;
|
||||
return {
|
||||
background: bg,
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
borderRadius: `${radius}rpx`,
|
||||
overflow: radius > 0 ? "hidden" : "visible",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
rowGap: `${Number(res.contentGap) || 0}rpx`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: res.bgColor || "#FFFFFF",
|
||||
padding: `20rpx ${res.paddingX ?? 0}rpx`,
|
||||
marginBottom: `${res.pageGap ?? 10}rpx`,
|
||||
borderRadius: `${res.borderRadius ?? 0}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
});
|
||||
const nameStyle = computed(() =>
|
||||
styled.value && props.res.titleColor ? { color: props.res.titleColor } : {}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
const params: any = { pageNumber: 1, pageSize: count.value };
|
||||
if (categoryId.value) params.categoryId = categoryId.value;
|
||||
getArticleList(params).then((res) => {
|
||||
if (res.data.success && res.data.result) {
|
||||
articleList.value = (res.data.result.records || []).slice(0, count.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function goDetail(item: any) {
|
||||
uni.navigateTo({ url: `/pages/passport/article?id=${item.id}` });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.article-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
padding: 8rpx 24rpx 20rpx;
|
||||
}
|
||||
.article-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
border-top: 2rpx solid #f5f5f5;
|
||||
}
|
||||
.tpl-article.styled .article-item {
|
||||
border-top: none;
|
||||
padding: 0;
|
||||
}
|
||||
.article-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.article-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -1,55 +1,190 @@
|
||||
<template>
|
||||
<view class="layout">
|
||||
<view class="banner-box">
|
||||
<u-swiper
|
||||
v-if="res && res.list && res.list.length"
|
||||
class="home-banner-swiper"
|
||||
:list="res.list"
|
||||
keyName="img"
|
||||
height="350rpx"
|
||||
imgMode="scaleToFill"
|
||||
interval="5000"
|
||||
duration="500"
|
||||
circular
|
||||
indicator
|
||||
@click="clickSwiper"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-swiper>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
function clickSwiper(index: number) {
|
||||
modelNavigateTo(props.res.list[index]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
|
||||
.banner-box {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.home-banner-swiper),
|
||||
:deep(.u-swiper),
|
||||
:deep(.u-swiper__wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.u-swiper__wrapper__item),
|
||||
:deep(.u-swiper__wrapper__item__wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.u-swiper__wrapper__item__wrapper__image) {
|
||||
width: 100% !important;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="layout" :style="wrapperStyle">
|
||||
<view class="banner-box" :style="cardMode ? {} : boxStyle">
|
||||
<u-swiper
|
||||
v-if="!cardMode && res && res.list && res.list.length"
|
||||
class="home-banner-swiper"
|
||||
:list="res.list"
|
||||
keyName="img"
|
||||
height="350rpx"
|
||||
imgMode="scaleToFill"
|
||||
interval="5000"
|
||||
duration="500"
|
||||
circular
|
||||
:indicator="!customIndicator"
|
||||
@change="onChange"
|
||||
@click="clickSwiper"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-swiper>
|
||||
<swiper
|
||||
v-if="cardMode && res && res.list && res.list.length"
|
||||
class="card-swiper"
|
||||
autoplay
|
||||
:interval="5000"
|
||||
:duration="500"
|
||||
circular
|
||||
previous-margin="68rpx"
|
||||
next-margin="68rpx"
|
||||
@change="onChange"
|
||||
>
|
||||
<swiper-item v-for="(item, i) in res.list" :key="i">
|
||||
<view class="card-item" :class="{ active: i === current }" @click="clickSwiper(i)">
|
||||
<image class="card-img" :style="boxStyle" :src="item.img" mode="scaleToFill" />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view
|
||||
v-if="customIndicator && res.indicatorStyle !== 'none' && res.list.length > 1"
|
||||
class="indicator"
|
||||
:class="'pos-' + (res.indicatorPosition || 'center')"
|
||||
>
|
||||
<view
|
||||
v-for="(item, i) in res.list"
|
||||
:key="i"
|
||||
class="dot"
|
||||
:class="[res.indicatorStyle || 'round', { active: i === current }]"
|
||||
:style="i === current ? { background: indicatorColor } : {}"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const current = ref(0);
|
||||
|
||||
// 装修面板保存过样式字段才启用自定义样式/指示器,老数据保持原样
|
||||
const customIndicator = computed(() => props.res && props.res.indicatorStyle !== undefined);
|
||||
|
||||
const cardMode = computed(() => props.res && props.res.swiperStyle === "card");
|
||||
|
||||
const indicatorColor = computed(() =>
|
||||
props.res.indicatorColorMode === "custom" && props.res.indicatorColor
|
||||
? props.res.indicatorColor
|
||||
: "var(--theme-primary, #ff3c2a)"
|
||||
);
|
||||
|
||||
const toRpx = (val: any) => `${Number(val) || 0}rpx`;
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (res.paddingTop === undefined) return {};
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
return {
|
||||
padding: `${toRpx(res.paddingTop)} ${toRpx(res.paddingX)} ${toRpx(res.paddingBottom)}`,
|
||||
background: bg,
|
||||
borderRadius: toRpx(res.borderRadius),
|
||||
marginBottom: toRpx(res.pageGap),
|
||||
};
|
||||
});
|
||||
|
||||
const boxStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (res.imgRadius === undefined) return {};
|
||||
return { borderRadius: toRpx(res.imgRadius) };
|
||||
});
|
||||
|
||||
function onChange(e: any) {
|
||||
current.value = typeof e === "number" ? e : e.current;
|
||||
}
|
||||
|
||||
function clickSwiper(index: number) {
|
||||
modelNavigateTo(props.res.list[index]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
|
||||
.banner-box {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-swiper {
|
||||
width: 100%;
|
||||
height: 350rpx;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 8rpx;
|
||||
box-sizing: border-box;
|
||||
transform: scale(0.92);
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
|
||||
.card-item.active {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.card-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
|
||||
&.pos-left {
|
||||
left: 24rpx;
|
||||
}
|
||||
&.pos-center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
&.pos-right {
|
||||
right: 24rpx;
|
||||
}
|
||||
|
||||
.dot {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
transition: background 0.3s ease;
|
||||
|
||||
&.round {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
&.line {
|
||||
width: 24rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 3rpx;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.home-banner-swiper),
|
||||
:deep(.u-swiper),
|
||||
:deep(.u-swiper__wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.u-swiper__wrapper__item),
|
||||
:deep(.u-swiper__wrapper__item__wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.u-swiper__wrapper__item__wrapper__image) {
|
||||
width: 100% !important;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
24
pages/tabbar/home/template/tpl_blank.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<view class="tpl-blank" :style="blankStyle"></view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const blankStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
const bg =
|
||||
res.bgColor1 !== undefined
|
||||
? !res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`
|
||||
: res.bgColor || "#f5f5f5";
|
||||
return {
|
||||
background: bg,
|
||||
height: `${res.height ?? 10}rpx`,
|
||||
margin: `0 ${res.paddingX ?? 0}rpx ${res.pageGap ?? 10}rpx`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
511
pages/tabbar/home/template/tpl_coupon.vue
Normal file
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<view class="tpl-coupon" :class="{ styled: styled }" :style="wrapperStyle" v-if="couponList.length || !styled">
|
||||
<view class="coupon-title" v-if="title">{{ title }}</view>
|
||||
|
||||
<!-- 老数据保持原样 -->
|
||||
<block v-if="!styled">
|
||||
<view
|
||||
class="coupon-item"
|
||||
v-for="item in couponList"
|
||||
:key="item.id"
|
||||
@click="receive(item)"
|
||||
>
|
||||
<view class="coupon-left">
|
||||
<view class="coupon-amount">
|
||||
<template v-if="item.couponType == 'DISCOUNT'">
|
||||
<text class="coupon-value">{{ item.couponDiscount }}</text>
|
||||
<text class="coupon-symbol">折</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="coupon-symbol">¥</text>
|
||||
<text class="coupon-value">{{ unitPrice(item.price) }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="coupon-threshold">满{{ unitPrice(item.consumeThreshold) }}元可用</view>
|
||||
</view>
|
||||
<view class="coupon-right">
|
||||
<view class="coupon-name">
|
||||
{{ item.storeName == 'platform' ? '全平台通用' : item.storeName + '店铺' }}
|
||||
</view>
|
||||
<view class="coupon-btn">立即领取</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 样式一:红色大卡横滑,右侧竖排领取 -->
|
||||
<scroll-view
|
||||
v-else-if="layoutStyle === '1'"
|
||||
scroll-x
|
||||
class="cp-scroll"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view
|
||||
class="cp1-item"
|
||||
v-for="item in couponList"
|
||||
:key="item.id"
|
||||
:style="itemGapStyle"
|
||||
@click="receive(item)"
|
||||
>
|
||||
<view class="cp1-left">
|
||||
<view class="cp1-amount">
|
||||
<template v-if="item.couponType == 'DISCOUNT'">
|
||||
<text class="cp1-value">{{ item.couponDiscount }}</text>
|
||||
<text class="cp1-symbol">折</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="cp1-symbol">¥</text>
|
||||
<text class="cp1-value">{{ unitPrice(item.price) }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="cp1-threshold">{{ thresholdText(item) }}</view>
|
||||
</view>
|
||||
<view class="cp1-right">
|
||||
<text>领</text><text>取</text>
|
||||
</view>
|
||||
<view class="cp-notch cp-notch-t"></view>
|
||||
<view class="cp-notch cp-notch-b"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 样式二:白底小卡,底部去领取按钮 -->
|
||||
<scroll-view
|
||||
v-else-if="layoutStyle === '2'"
|
||||
scroll-x
|
||||
class="cp-scroll"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view
|
||||
class="cp2-item"
|
||||
v-for="item in couponList"
|
||||
:key="item.id"
|
||||
:style="itemGapStyle"
|
||||
@click="receive(item)"
|
||||
>
|
||||
<view class="cp2-top">
|
||||
<view class="cp2-amount">
|
||||
<template v-if="item.couponType == 'DISCOUNT'">
|
||||
<text class="cp2-value">{{ item.couponDiscount }}</text>
|
||||
<text class="cp2-symbol">折</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="cp2-symbol">¥</text>
|
||||
<text class="cp2-value">{{ unitPrice(item.price) }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="cp2-threshold">{{ thresholdText(item) }}</view>
|
||||
</view>
|
||||
<view class="cp2-btn">去领取</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 样式三:浅粉虚线卡,右侧竖排立即领取 -->
|
||||
<scroll-view
|
||||
v-else-if="layoutStyle === '3'"
|
||||
scroll-x
|
||||
class="cp-scroll"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view
|
||||
class="cp3-item"
|
||||
v-for="item in couponList"
|
||||
:key="item.id"
|
||||
:style="itemGapStyle"
|
||||
@click="receive(item)"
|
||||
>
|
||||
<view class="cp3-left">
|
||||
<view class="cp3-amount">
|
||||
<template v-if="item.couponType == 'DISCOUNT'">
|
||||
<text class="cp3-value">{{ item.couponDiscount }}</text>
|
||||
<text class="cp3-symbol">折</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="cp3-symbol">¥</text>
|
||||
<text class="cp3-value">{{ unitPrice(item.price) }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="cp3-threshold">{{ thresholdText(item) }}</view>
|
||||
</view>
|
||||
<view class="cp3-right">
|
||||
<text>立</text><text>即</text><text>领</text><text>取</text>
|
||||
</view>
|
||||
<view class="cp-notch cp-notch-t"></view>
|
||||
<view class="cp-notch cp-notch-b"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 样式四:浅粉渐变 + 右侧渐变竖条领取 -->
|
||||
<scroll-view v-else scroll-x class="cp-scroll" :show-scrollbar="false">
|
||||
<view
|
||||
class="cp4-item"
|
||||
v-for="item in couponList"
|
||||
:key="item.id"
|
||||
:style="itemGapStyle"
|
||||
@click="receive(item)"
|
||||
>
|
||||
<view class="cp4-left">
|
||||
<view class="cp4-amount">
|
||||
<template v-if="item.couponType == 'DISCOUNT'">
|
||||
<text class="cp4-value">{{ item.couponDiscount }}</text>
|
||||
<text class="cp4-symbol">折</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="cp4-symbol">¥</text>
|
||||
<text class="cp4-value">{{ unitPrice(item.price) }}</text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="cp4-threshold">{{ thresholdText(item) }}</view>
|
||||
</view>
|
||||
<view class="cp4-right">
|
||||
<text>领</text><text>取</text>
|
||||
</view>
|
||||
<view class="cp-notch cp-notch-t"></view>
|
||||
<view class="cp-notch cp-notch-b"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { getAllCoupons } from "@/api/promotions.js";
|
||||
import { receiveCoupons } from "@/api/members.js";
|
||||
import { isLogin, navigateToLogin, unitPrice } from "@/utils/filters.js";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const couponList = ref<any[]>([]);
|
||||
|
||||
const title = computed(() => props.res?.list?.[0]?.title || "");
|
||||
const count = computed(() => props.res?.list?.[0]?.count || 5);
|
||||
const layoutStyle = computed(() => (props.res && props.res.layoutStyle) || "1");
|
||||
|
||||
// 面板保存过样式字段才启用自定义样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
const itemGapStyle = computed(() => {
|
||||
if (!styled.value) return {};
|
||||
const gap = props.res?.contentGap !== undefined ? Number(props.res.contentGap) || 0 : 5;
|
||||
return { marginRight: `${gap}rpx` };
|
||||
});
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (styled.value) {
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
const radius = Number(res.borderRadius) || 0;
|
||||
return {
|
||||
background: bg,
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
borderRadius: `${radius}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: res.bgColor || "#FFFFFF",
|
||||
padding: `20rpx ${res.paddingX ?? 0}rpx`,
|
||||
marginBottom: `${res.pageGap ?? 10}rpx`,
|
||||
borderRadius: `${res.borderRadius ?? 0}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getAllCoupons({ pageNumber: 1, pageSize: count.value }).then((res) => {
|
||||
if (res.data.code == 200 && res.data.result) {
|
||||
couponList.value = (res.data.result.records || []).slice(0, count.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function thresholdText(item: any) {
|
||||
const t = Number(unitPrice(item.consumeThreshold));
|
||||
return t > 0 ? `满${unitPrice(item.consumeThreshold)}元可用` : "无门槛使用";
|
||||
}
|
||||
|
||||
function receive(item: any) {
|
||||
if (!isLogin("auth")) {
|
||||
navigateToLogin("redirectTo");
|
||||
return;
|
||||
}
|
||||
receiveCoupons(item.id).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({ title: "领取成功", icon: "none" });
|
||||
} else {
|
||||
uni.showToast({ title: res.data.message, icon: "none" });
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tpl-coupon {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.coupon-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
padding: 8rpx 24rpx 20rpx;
|
||||
}
|
||||
.coupon-item {
|
||||
display: flex;
|
||||
margin: 0 20rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(90deg, #ffece8, #fff5f4);
|
||||
}
|
||||
.tpl-coupon.styled .coupon-item {
|
||||
margin: 0;
|
||||
}
|
||||
.coupon-left {
|
||||
width: 180rpx;
|
||||
padding: 24rpx 0;
|
||||
text-align: center;
|
||||
color: var(--theme-primary, #e1251b);
|
||||
border-right: 2rpx dashed #f0c0bb;
|
||||
}
|
||||
.coupon-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.coupon-symbol {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.coupon-threshold {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.coupon-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.coupon-name {
|
||||
color: #333;
|
||||
}
|
||||
.coupon-btn {
|
||||
background: var(--theme-primary, #e1251b);
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
/* 参考 CRMEB 优惠券组件 */
|
||||
.tpl-coupon.styled .coupon-title {
|
||||
padding: 0 0 16rpx;
|
||||
}
|
||||
.cp-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cp1-item:last-child,
|
||||
.cp2-item:last-child,
|
||||
.cp3-item:last-child,
|
||||
.cp4-item:last-child {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
.cp-notch {
|
||||
position: absolute;
|
||||
right: 60rpx;
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
}
|
||||
.cp-notch-t {
|
||||
top: -8rpx;
|
||||
}
|
||||
.cp-notch-b {
|
||||
bottom: -8rpx;
|
||||
}
|
||||
|
||||
/* 样式一:红色大卡 */
|
||||
.cp1-item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 264rpx;
|
||||
height: 132rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: linear-gradient(135deg, #ff5a36, #e1251b);
|
||||
color: #fff;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cp1-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
.cp1-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cp1-symbol {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.cp1-threshold {
|
||||
font-size: 20rpx;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
.cp1-right {
|
||||
width: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
border-left: 2rpx dashed rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.cp1-item .cp-notch {
|
||||
right: 52rpx;
|
||||
}
|
||||
|
||||
/* 样式二:白底小卡 */
|
||||
.cp2-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: 148rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #fff;
|
||||
border-left: 4rpx solid #f5483a;
|
||||
border-right: 4rpx solid #f5483a;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cp2-top {
|
||||
padding: 16rpx 0 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.cp2-value {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #e1251b;
|
||||
}
|
||||
.cp2-symbol {
|
||||
font-size: 20rpx;
|
||||
color: #e1251b;
|
||||
}
|
||||
.cp2-threshold {
|
||||
font-size: 18rpx;
|
||||
color: #999;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
.cp2-btn {
|
||||
background: linear-gradient(90deg, #ff6a3d, #e1251b);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
/* 样式三:浅粉虚线卡 */
|
||||
.cp3-item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 276rpx;
|
||||
height: 120rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #fdf1ec;
|
||||
border: 2rpx dashed #f5483a;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cp3-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
.cp3-value {
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
color: #e1251b;
|
||||
}
|
||||
.cp3-symbol {
|
||||
font-size: 20rpx;
|
||||
color: #e1251b;
|
||||
}
|
||||
.cp3-threshold {
|
||||
font-size: 20rpx;
|
||||
color: #e1251b;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
.cp3-right {
|
||||
width: 64rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
color: #e1251b;
|
||||
border-left: 2rpx dashed #f5483a;
|
||||
}
|
||||
.cp3-item .cp-notch {
|
||||
right: 56rpx;
|
||||
}
|
||||
|
||||
/* 样式四:浅粉渐变 + 渐变竖条 */
|
||||
.cp4-item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 232rpx;
|
||||
height: 108rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cp4-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 20rpx;
|
||||
background: linear-gradient(135deg, #fff3ec, #ffe0d3);
|
||||
}
|
||||
.cp4-value {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #e1251b;
|
||||
}
|
||||
.cp4-symbol {
|
||||
font-size: 20rpx;
|
||||
color: #e1251b;
|
||||
}
|
||||
.cp4-threshold {
|
||||
font-size: 18rpx;
|
||||
color: #e1251b;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
.cp4-right {
|
||||
width: 52rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
background: linear-gradient(180deg, #ff6a3d, #e1251b);
|
||||
}
|
||||
.cp4-item .cp-notch {
|
||||
right: 44rpx;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
}
|
||||
.cp4-item .cp-notch-t {
|
||||
top: -6rpx;
|
||||
}
|
||||
.cp4-item .cp-notch-b {
|
||||
bottom: -6rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,21 @@
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<u-image width="140rpx" mode="aspectFit" height="140rpx" @click="modelNavigateTo(item)" class="image-mode" v-for="(item,index) in res.list" :key="index" :src="item.img" alt="">
|
||||
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<u-image width="100%" mode="aspectFit" height="140rpx" @click="modelNavigateTo(item)" class="image-mode flex-img" :style="imgStyle" v-for="(item,index) in res.list" :key="index" :src="item.img" alt="">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -20,4 +25,8 @@ defineProps<{ res: any }>();
|
||||
justify-content: center;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
.flex-img {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<u-image height="175rpx" mode="aspectFit" width="175rpx" @click="modelNavigateTo(item)" class="image-mode" :src="item.img" v-for="(item,index) in res.list" :key="index">
|
||||
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<u-image height="175rpx" mode="aspectFit" width="100%" @click="modelNavigateTo(item)" class="image-mode flex-img" :style="imgStyle" :src="item.img" v-for="(item,index) in res.list" :key="index">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
.layout {
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
.flex-img {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="layout">
|
||||
<view class="flex-one">
|
||||
<view class="layout" :style="wrapperStyle">
|
||||
<view class="flex-one" :style="contentStyle">
|
||||
<u-image
|
||||
v-if="!hasHotZone"
|
||||
@click="modelNavigateTo(res.list[0])"
|
||||
@@ -25,6 +25,27 @@ const hasHotZone = computed(() => {
|
||||
const zoneInfo = props.res?.list?.[0]?.zoneInfo;
|
||||
return Array.isArray(zoneInfo) ? zoneInfo.length > 0 : !!zoneInfo;
|
||||
});
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const r = props.res || {};
|
||||
const bg =
|
||||
r.bgColor1 !== undefined
|
||||
? !r.bgColor2 || r.bgColor1 === r.bgColor2
|
||||
? r.bgColor1
|
||||
: `linear-gradient(180deg, ${r.bgColor1}, ${r.bgColor2})`
|
||||
: r.bgColor ?? "transparent";
|
||||
return {
|
||||
background: bg,
|
||||
padding: `${r.paddingTop ?? 0}rpx ${r.paddingX ?? 0}rpx ${r.paddingBottom ?? 0}rpx`,
|
||||
marginBottom: `${r.pageGap ?? 0}rpx`,
|
||||
borderRadius: `${r.borderRadius ?? 0}rpx`,
|
||||
overflow: (r.borderRadius ?? 0) > 0 ? "hidden" : "visible",
|
||||
};
|
||||
});
|
||||
|
||||
const contentStyle = computed(() => ({
|
||||
borderRadius: `${props.res?.imgRadius ?? 0}rpx`,
|
||||
}));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -33,4 +54,4 @@ const hasHotZone = computed(() => {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<u-image @click="modelNavigateTo(item)" height="240rpx" width="240rpx" class="image-mode" :src="item.img" v-for="(item, index) in res.list" :key="index">
|
||||
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<u-image @click="modelNavigateTo(item)" height="240rpx" width="100%" class="image-mode flex-img" :style="imgStyle" :src="item.img" v-for="(item, index) in res.list" :key="index">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -22,4 +27,8 @@ defineProps<{ res: any }>();
|
||||
justify-content: center;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
.flex-img {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<view class="layout">
|
||||
<view class="flex-two">
|
||||
<view class="flex-item" @click="modelNavigateTo(res.list[0])">
|
||||
<view class="layout" :style="wrapperStyle">
|
||||
<view class="flex-two" :style="gapStyle">
|
||||
<view class="flex-item" :style="imgStyle" @click="modelNavigateTo(res.list[0])">
|
||||
<u-image height="250rpx" width="100%" mode="scaleToFill" :src="res.list[0].img" alt>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="flex-item" @click="modelNavigateTo(res.list[1])">
|
||||
<view class="flex-item" :style="imgStyle" @click="modelNavigateTo(res.list[1])">
|
||||
<u-image height="250rpx" width="100%" mode="scaleToFill" :src="res.list[1].img" alt>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
@@ -15,9 +15,14 @@
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -27,6 +32,7 @@ defineProps<{ res: any }>();
|
||||
overflow: hidden;
|
||||
}
|
||||
.flex-item {
|
||||
width: 50%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,265 +1,704 @@
|
||||
<template>
|
||||
<view class="layout">
|
||||
<u-sticky>
|
||||
<view class="goods-cell-title">
|
||||
<view
|
||||
class="goods-item-title"
|
||||
:class="{ 'selected-title': selected.index == index }"
|
||||
@click="handleClickTitle(title, index)"
|
||||
v-for="(title, index) in res.list[0].titleWay"
|
||||
:key="index"
|
||||
>
|
||||
<h4 class="h4">{{ title.title }}</h4>
|
||||
<view class="title-desc">{{ title.desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-sticky>
|
||||
<view class="goods-list">
|
||||
<template v-for="(item, item_index) in res.list[0].listWay" :key="item_index">
|
||||
<view
|
||||
v-if="
|
||||
item.___index != undefined
|
||||
? selected.index == item.___index
|
||||
: selected.val == item.type
|
||||
"
|
||||
@click="handleClick(item)"
|
||||
class="goods-item"
|
||||
>
|
||||
<view class="goods-img">
|
||||
<u-image
|
||||
:src="item.img"
|
||||
height="350rpx"
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-title">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="goods-bottom">
|
||||
<view class="goods-price">
|
||||
¥<span class="price-int"
|
||||
>{{ goodsFormatPrice(item.price)[0] }} </span
|
||||
>.{{ goodsFormatPrice(item.price)[1] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="currentTitleWay?.bindCategory && goodsData.length"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in goodsData"
|
||||
:key="index"
|
||||
class="goods-item"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<view class="goods-img">
|
||||
<u-image
|
||||
:src="item.thumbnail"
|
||||
height="350rpx"
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-title">
|
||||
{{ item.goodsName }}
|
||||
</view>
|
||||
<view class="goods-bottom">
|
||||
<view class="goods-price">
|
||||
¥<span class="price-int"
|
||||
>{{ goodsFormatPrice(item.price)[0] }} </span
|
||||
>.{{ goodsFormatPrice(item.price)[1] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted, onUnmounted, computed } from "vue";
|
||||
import { getGoodsList } from "@/api/goods.js";
|
||||
import { goodsFormatPrice } from "@/utils/filters.js";
|
||||
|
||||
const props = defineProps<{ res: any; enableBottomLoad?: boolean }>();
|
||||
|
||||
const selected = reactive({
|
||||
index: 0,
|
||||
val: "",
|
||||
});
|
||||
|
||||
const currentTitleWay = computed(() => {
|
||||
return props.res?.list?.[0]?.titleWay?.[selected.index]
|
||||
})
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
categoryId: "",
|
||||
});
|
||||
const goodsData = ref<any[]>([]);
|
||||
const goodsResult = ref<any>("");
|
||||
|
||||
watch(
|
||||
() => props.res,
|
||||
(val) => {
|
||||
if (!val?.list?.[0]) return
|
||||
const firstListWay = val.list[0].listWay?.[0]
|
||||
selected.val = firstListWay?.type || ''
|
||||
const firstTitleWay = val.list[0].titleWay?.[0]
|
||||
if (firstTitleWay?.bindCategory) {
|
||||
initGoods(firstTitleWay)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
uni.$on("onReachBottom", () => {
|
||||
if (
|
||||
props.enableBottomLoad &&
|
||||
goodsResult.value.totalElements >=
|
||||
params.pageNumber * params.pageSize
|
||||
) {
|
||||
params.pageNumber++;
|
||||
initGoods(props.res.list[0].titleWay[selected.index]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off("onReachBottom");
|
||||
});
|
||||
|
||||
function handleClick(item: any) {
|
||||
if (!item?.id || !item?.goodsId) return
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
|
||||
async function initGoods(val: any) {
|
||||
if (props.enableBottomLoad) params.pageSize = 20;
|
||||
val ? (params.categoryId = val.bindCategory.id) : "";
|
||||
const res = await getGoodsList(params);
|
||||
if (res.data.success) {
|
||||
goodsResult.value = res.data.result;
|
||||
const result = res.data.result.records;
|
||||
goodsData.value.push(...result);
|
||||
console.log(goodsData.value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickTitle(val: any, index: number) {
|
||||
selected.index = index;
|
||||
selected.val = val.title;
|
||||
if (val.bindCategory) {
|
||||
params.pageNumber = 1;
|
||||
goodsData.value = [];
|
||||
initGoods(val);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$w_94: 94%;
|
||||
|
||||
.layout {
|
||||
padding: 8px 0;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.selected-title {
|
||||
.h4 {
|
||||
font-size: 30rpx;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.title-desc {
|
||||
font-weight: bold;
|
||||
color: $main-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-cell-title {
|
||||
background: #f9f9f9;
|
||||
padding: 10px;
|
||||
transition: 0.35s;
|
||||
display: flex;
|
||||
|
||||
> .goods-item-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.h4 {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.title-desc {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
width: 50%;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 0.4em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
// width: 158px;
|
||||
width: $w_94;
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-desc {
|
||||
border-bottom-left-radius: 20rpx;
|
||||
border-bottom-right-radius: 20rpx;
|
||||
width: $w_94;
|
||||
background: #fff;
|
||||
padding: 8rpx 0 8rpx 8rpx;
|
||||
margin: 0 auto;
|
||||
> .goods-title {
|
||||
font-size: 24rpx;
|
||||
height: 67rpx;
|
||||
display: -webkit-box;
|
||||
font-weight: 500;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .goods-bottom {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
> .goods-price {
|
||||
line-height: 2;
|
||||
color: $main-color;
|
||||
.price-int {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<!-- 新版商品列表 -->
|
||||
<view v-if="isNew" class="goods-new" :style="wrapperStyle">
|
||||
<view v-if="displayGoods.length" class="g-list" :class="'mode-' + layout" :style="listStyle">
|
||||
<view
|
||||
class="g-item"
|
||||
v-for="(g, i) in displayGoods"
|
||||
:key="i"
|
||||
:style="itemStyle"
|
||||
@click="goDetail(g)"
|
||||
>
|
||||
<view class="g-imgbox">
|
||||
<image :src="g.img" mode="aspectFill" class="g-img" />
|
||||
</view>
|
||||
<view class="g-info">
|
||||
<view v-if="showName" class="g-name" :style="nameStyle">{{ g.name }}</view>
|
||||
<view v-if="showPrice" class="g-price" :style="priceStyle">
|
||||
¥{{ goodsFormatPrice(g.price)[0] }}.{{ goodsFormatPrice(g.price)[1] }}
|
||||
</view>
|
||||
<view class="g-bottom">
|
||||
<text
|
||||
v-if="showSales && g.sales != null"
|
||||
class="g-sales"
|
||||
:style="salesStyle"
|
||||
>已售{{ g.sales }}件</text
|
||||
>
|
||||
<view
|
||||
v-if="cartShow"
|
||||
class="g-cart"
|
||||
:class="'cart-' + cartStyle"
|
||||
:style="{ background: priceColor }"
|
||||
@click.stop="onCartBtn(g)"
|
||||
>
|
||||
<text v-if="cartStyle === 'text'" class="cart-text-label">加购</text>
|
||||
<text v-else-if="cartStyle === 'plus'" class="cart-plus-label">+</text>
|
||||
<u-icon v-else name="shopping-cart" color="#ffffff" size="28rpx"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="g-empty">暂无商品</view>
|
||||
</view>
|
||||
|
||||
<!-- 旧版:分类标签 + 绑定商品 -->
|
||||
<view v-else class="layout">
|
||||
<u-sticky>
|
||||
<view class="goods-cell-title">
|
||||
<view
|
||||
class="goods-item-title"
|
||||
:class="{ 'selected-title': selected.index == index }"
|
||||
@click="handleClickTitle(title, index)"
|
||||
v-for="(title, index) in res.list[0].titleWay"
|
||||
:key="index"
|
||||
>
|
||||
<h4 class="h4">{{ title.title }}</h4>
|
||||
<view class="title-desc">{{ title.desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-sticky>
|
||||
<view class="goods-list">
|
||||
<template v-for="(item, item_index) in res.list[0].listWay" :key="item_index">
|
||||
<view
|
||||
v-if="
|
||||
item.___index != undefined
|
||||
? selected.index == item.___index
|
||||
: selected.val == item.type
|
||||
"
|
||||
@click="handleClick(item)"
|
||||
class="goods-item"
|
||||
>
|
||||
<view class="goods-img">
|
||||
<u-image
|
||||
:src="item.img"
|
||||
height="350rpx"
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-title">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="goods-bottom">
|
||||
<view class="goods-price">
|
||||
¥<span class="price-int"
|
||||
>{{ goodsFormatPrice(item.price)[0] }} </span
|
||||
>.{{ goodsFormatPrice(item.price)[1] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="currentTitleWay?.bindCategory && goodsData.length"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in goodsData"
|
||||
:key="index"
|
||||
class="goods-item"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<view class="goods-img">
|
||||
<u-image
|
||||
:src="item.thumbnail"
|
||||
height="350rpx"
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-title">
|
||||
{{ item.goodsName }}
|
||||
</view>
|
||||
<view class="goods-bottom">
|
||||
<view class="goods-price">
|
||||
¥<span class="price-int"
|
||||
>{{ goodsFormatPrice(item.price)[0] }} </span
|
||||
>.{{ goodsFormatPrice(item.price)[1] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted, onUnmounted, computed } from "vue";
|
||||
import { getGoodsList } from "@/api/goods.js";
|
||||
import { addToCart } from "@/api/trade.js";
|
||||
import { goodsFormatPrice } from "@/utils/filters.js";
|
||||
import storage from "@/utils/storage.js";
|
||||
|
||||
const props = defineProps<{ res: any; enableBottomLoad?: boolean }>();
|
||||
|
||||
const selected = reactive({
|
||||
index: 0,
|
||||
val: "",
|
||||
});
|
||||
|
||||
// ---------- 新版商品列表 ----------
|
||||
const first = computed(() => props.res?.list?.[0] || {});
|
||||
const isNew = computed(() => first.value.layout !== undefined);
|
||||
const layout = computed(() => first.value.layout || "list");
|
||||
const showName = computed(() => first.value.showName !== false);
|
||||
const showPrice = computed(() => first.value.showPrice !== false);
|
||||
const showSales = computed(() => first.value.showSales !== false);
|
||||
const cartShow = computed(() => first.value.cartShow !== false);
|
||||
const cartStyle = computed(() => first.value.cartStyle || "plus");
|
||||
const priceColor = computed(() =>
|
||||
props.res?.toneMode === "custom" && props.res?.priceColor
|
||||
? props.res.priceColor
|
||||
: "var(--theme-primary, #ff3c2a)"
|
||||
);
|
||||
const priceStyle = computed(() => ({ color: priceColor.value }));
|
||||
const nameStyle = computed(() =>
|
||||
props.res?.titleColor ? { color: props.res.titleColor } : {}
|
||||
);
|
||||
const salesStyle = computed(() =>
|
||||
props.res?.salesColor ? { color: props.res.salesColor } : {}
|
||||
);
|
||||
const rpx = (v: any) => `${Number(v) || 0}rpx`;
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1 || "#ffffff"
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
return {
|
||||
background: bg,
|
||||
padding: `${rpx(res.paddingTop)} ${rpx(res.paddingX)} ${rpx(res.paddingBottom)}`,
|
||||
marginBottom: rpx(res.pageGap),
|
||||
borderRadius: rpx(res.borderRadius),
|
||||
overflow: Number(res.borderRadius) > 0 ? "hidden" : "visible",
|
||||
};
|
||||
});
|
||||
const listStyle = computed(() => {
|
||||
const gap = rpx(props.res?.contentGap ?? 10);
|
||||
return { gap, "--g-gap": gap };
|
||||
});
|
||||
const itemStyle = computed(() => ({
|
||||
borderRadius: rpx(props.res?.contentRadius ?? 7),
|
||||
overflow: Number(props.res?.contentRadius ?? 7) > 0 ? "hidden" : "visible",
|
||||
}));
|
||||
|
||||
const newGoodsData = ref<any[]>([]);
|
||||
|
||||
const displayGoods = computed(() => {
|
||||
if (!isNew.value) return [];
|
||||
return newGoodsData.value;
|
||||
});
|
||||
|
||||
// 触底加载(新版)
|
||||
const newParams = reactive({ pageNumber: 1 });
|
||||
const newTotal = ref(0);
|
||||
|
||||
function buildQuery() {
|
||||
const item = first.value;
|
||||
const params: Record<string, any> = {
|
||||
pageNumber: newParams.pageNumber,
|
||||
pageSize: item.count || 6,
|
||||
};
|
||||
if (item.dataMode === "brand" && item.brandId) params.brandId = item.brandId;
|
||||
if (item.dataMode === "store" && item.storeId) params.storeId = item.storeId;
|
||||
if (item.dataMode === "category" && item.categoryId) params.categoryId = item.categoryId;
|
||||
if (item.dataMode === "goods") {
|
||||
const ids = (item.goodsList || []).map((g: any) => g.id).filter(Boolean);
|
||||
if (!ids.length) return null;
|
||||
params.ids = ids.join(",");
|
||||
params.pageSize = ids.length;
|
||||
}
|
||||
if (item.sortType === "sales") {
|
||||
params.sort = "buyCount";
|
||||
params.order = "desc";
|
||||
} else if (item.sortType === "price") {
|
||||
params.sort = "price";
|
||||
params.order = "asc";
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
async function loadNewGoods(reset = true) {
|
||||
const params = buildQuery();
|
||||
if (!params) {
|
||||
newGoodsData.value = [];
|
||||
newTotal.value = 0;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getGoodsList(params);
|
||||
if (!res.data.success) return;
|
||||
const result = res.data.result || {};
|
||||
const records = result.records || [];
|
||||
const mapped = records.map((g: any) => ({
|
||||
id: g.id,
|
||||
goodsId: g.goodsId,
|
||||
name: g.goodsName,
|
||||
price: g.price,
|
||||
img: g.thumbnail || g.small,
|
||||
sales: g.buyCount != null ? g.buyCount : null,
|
||||
}));
|
||||
if (reset) {
|
||||
// 指定商品:按选择顺序排列(兼容 SKU id 与 goodsId)
|
||||
if (first.value.dataMode === "goods") {
|
||||
const byId: Record<string, any> = {};
|
||||
mapped.forEach((g: any) => {
|
||||
if (g.id) byId[g.id] = g;
|
||||
if (g.goodsId) byId[g.goodsId] = g;
|
||||
});
|
||||
newGoodsData.value = (first.value.goodsList || [])
|
||||
.map((g: any) => byId[g.id] || byId[g.goodsId])
|
||||
.filter(Boolean);
|
||||
} else {
|
||||
newGoodsData.value = mapped;
|
||||
}
|
||||
} else {
|
||||
newGoodsData.value.push(...mapped);
|
||||
}
|
||||
newTotal.value = result.totalElements ?? newGoodsData.value.length;
|
||||
} catch (e) {
|
||||
// 静默失败,保持已有数据
|
||||
}
|
||||
}
|
||||
|
||||
function goDetail(g: any) {
|
||||
if (!g?.id || !g?.goodsId) return;
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${g.id}&goodsId=${g.goodsId}`,
|
||||
});
|
||||
}
|
||||
|
||||
function onCartBtn(g: any) {
|
||||
if (first.value.cartAction !== "add") {
|
||||
goDetail(g);
|
||||
return;
|
||||
}
|
||||
if (!g?.id) return;
|
||||
if (!storage.getAccessToken()) {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.navigateTo({ url: "/pages/passport/wechatMPLogin" });
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.navigateTo({ url: "/pages/passport/login" });
|
||||
// #endif
|
||||
return;
|
||||
}
|
||||
addToCart({ skuId: g.id, num: 1 }).then((res: any) => {
|
||||
uni.showToast({
|
||||
title: res.data.success ? "已加入购物车" : res.data.message || "加购失败",
|
||||
icon: "none",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [
|
||||
first.value.dataMode,
|
||||
first.value.categoryId,
|
||||
first.value.brandId,
|
||||
first.value.storeId,
|
||||
first.value.count,
|
||||
first.value.sortType,
|
||||
first.value.goodsList,
|
||||
],
|
||||
() => {
|
||||
if (!isNew.value) return;
|
||||
newParams.pageNumber = 1;
|
||||
loadNewGoods(true);
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
// ---------- 旧版逻辑 ----------
|
||||
const currentTitleWay = computed(() => {
|
||||
return props.res?.list?.[0]?.titleWay?.[selected.index]
|
||||
})
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 100,
|
||||
categoryId: "",
|
||||
});
|
||||
const goodsData = ref<any[]>([]);
|
||||
const goodsResult = ref<any>("");
|
||||
|
||||
watch(
|
||||
() => props.res,
|
||||
(val) => {
|
||||
if (!val?.list?.[0]) return
|
||||
if (val.list[0].layout !== undefined) return
|
||||
const firstListWay = val.list[0].listWay?.[0]
|
||||
selected.val = firstListWay?.type || ''
|
||||
const firstTitleWay = val.list[0].titleWay?.[0]
|
||||
if (firstTitleWay?.bindCategory) {
|
||||
initGoods(firstTitleWay)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
uni.$on("onReachBottom", () => {
|
||||
if (!props.enableBottomLoad) return;
|
||||
if (isNew.value) {
|
||||
// 新版:仅分类/品牌/商户模式支持触底加载
|
||||
if (first.value.dataMode === "goods") return;
|
||||
if (newTotal.value > newGoodsData.value.length) {
|
||||
newParams.pageNumber++;
|
||||
loadNewGoods(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
goodsResult.value.totalElements >=
|
||||
params.pageNumber * params.pageSize
|
||||
) {
|
||||
params.pageNumber++;
|
||||
initGoods(props.res.list[0].titleWay[selected.index]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
uni.$off("onReachBottom");
|
||||
});
|
||||
|
||||
function handleClick(item: any) {
|
||||
if (!item?.id || !item?.goodsId) return
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
|
||||
async function initGoods(val: any) {
|
||||
if (props.enableBottomLoad) params.pageSize = 20;
|
||||
val ? (params.categoryId = val.bindCategory.id) : "";
|
||||
const res = await getGoodsList(params);
|
||||
if (res.data.success) {
|
||||
goodsResult.value = res.data.result;
|
||||
const result = res.data.result.records;
|
||||
goodsData.value.push(...result);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickTitle(val: any, index: number) {
|
||||
selected.index = index;
|
||||
selected.val = val.title;
|
||||
if (val.bindCategory) {
|
||||
params.pageNumber = 1;
|
||||
goodsData.value = [];
|
||||
initGoods(val);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$w_94: 94%;
|
||||
|
||||
// ---------- 新版 ----------
|
||||
.goods-new {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.g-empty {
|
||||
padding: 60rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.g-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.g-item {
|
||||
background: #fff;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.g-imgbox {
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.g-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.g-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.g-name {
|
||||
font-size: 26rpx;
|
||||
color: #282828;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.g-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.g-price {
|
||||
color: $main-color;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.g-sales {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.g-cart {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: $main-color;
|
||||
}
|
||||
|
||||
.cart-plus,
|
||||
.cart-cart {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cart-plus-label {
|
||||
font-size: 30rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cart-text {
|
||||
border-radius: 24rpx;
|
||||
padding: 6rpx 20rpx;
|
||||
}
|
||||
|
||||
.cart-text-label {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 卡片:左图右文(参考 CRMEB 详细列表,左侧 224rpx 缩略图,右侧标题置顶、价格销量置底) */
|
||||
.mode-card {
|
||||
flex-direction: column;
|
||||
.g-item {
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.g-imgbox {
|
||||
width: 224rpx;
|
||||
height: 224rpx;
|
||||
margin-right: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.g-info {
|
||||
min-height: 224rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.g-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 40rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.g-price {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
margin-top: auto;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.g-bottom {
|
||||
margin-top: 0;
|
||||
}
|
||||
.g-sales {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.g-cart {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 列表:两列网格 */
|
||||
.mode-list {
|
||||
.g-item {
|
||||
width: calc((100% - var(--g-gap, 10rpx)) / 2);
|
||||
flex-direction: column;
|
||||
}
|
||||
.g-imgbox {
|
||||
width: 100%;
|
||||
height: 330rpx;
|
||||
}
|
||||
.g-info {
|
||||
padding: 12rpx;
|
||||
min-height: 120rpx;
|
||||
}
|
||||
.g-name {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 三列 */
|
||||
.mode-three {
|
||||
.g-item {
|
||||
width: calc((100% - var(--g-gap, 10rpx) * 2) / 3);
|
||||
flex-direction: column;
|
||||
}
|
||||
.g-imgbox {
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
}
|
||||
.g-info {
|
||||
padding: 10rpx;
|
||||
min-height: 96rpx;
|
||||
}
|
||||
.g-name {
|
||||
-webkit-line-clamp: 1;
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.g-price {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.g-sales {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大图:一行一个 */
|
||||
.mode-big {
|
||||
flex-direction: column;
|
||||
.g-item {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.g-imgbox {
|
||||
width: 100%;
|
||||
height: 420rpx;
|
||||
}
|
||||
.g-info {
|
||||
padding: 16rpx;
|
||||
min-height: 110rpx;
|
||||
}
|
||||
.g-name {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 旧版 ----------
|
||||
.layout {
|
||||
padding: 8px 0;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.selected-title {
|
||||
.h4 {
|
||||
font-size: 30rpx;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.title-desc {
|
||||
font-weight: bold;
|
||||
color: $main-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-cell-title {
|
||||
background: #f9f9f9;
|
||||
padding: 10px;
|
||||
transition: 0.35s;
|
||||
display: flex;
|
||||
|
||||
> .goods-item-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.h4 {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.title-desc {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
width: 50%;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 0.4em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: $w_94;
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-desc {
|
||||
border-bottom-left-radius: 20rpx;
|
||||
border-bottom-right-radius: 20rpx;
|
||||
width: $w_94;
|
||||
background: #fff;
|
||||
padding: 8rpx 0 8rpx 8rpx;
|
||||
margin: 0 auto;
|
||||
> .goods-title {
|
||||
font-size: 24rpx;
|
||||
height: 67rpx;
|
||||
display: -webkit-box;
|
||||
font-weight: 500;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .goods-bottom {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
> .goods-price {
|
||||
line-height: 2;
|
||||
color: $main-color;
|
||||
.price-int {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
497
pages/tabbar/home/template/tpl_home_comb.vue
Normal file
@@ -0,0 +1,497 @@
|
||||
<template>
|
||||
<view class="tpl-home-comb" :style="wrapperStyle">
|
||||
<!-- 高斯模糊轮播图背景 + 底部渐变遮罩 -->
|
||||
<view class="comb-bg">
|
||||
<image
|
||||
v-if="currentBannerImg"
|
||||
class="comb-bg-img"
|
||||
:src="currentBannerImg"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="comb-mask"></view>
|
||||
</view>
|
||||
|
||||
<!-- 顶部状态栏占位(小程序/App) -->
|
||||
<view v-if="statusBarHeight" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
|
||||
<!-- 1. 搜索栏 -->
|
||||
<view class="comb-search-row" :style="searchRowStyle">
|
||||
<image
|
||||
v-if="res.searType === 'logo' && res.logoUrl"
|
||||
class="comb-logo"
|
||||
:src="res.logoUrl"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="comb-search-box" :style="searchBoxStyle" @click="onSearchTap">
|
||||
<u-icon name="search" :color="res.textColor || '#303133'" size="28rpx" />
|
||||
<text class="comb-search-text">{{ displayHotWord }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. 分类选项卡 -->
|
||||
<view v-if="res.showTabs !== false" class="comb-tabs-wrap">
|
||||
<view class="comb-tabs">
|
||||
<scroll-view scroll-x class="comb-tabs-scroll" :show-scrollbar="false">
|
||||
<view class="comb-tabs-inner">
|
||||
<view
|
||||
v-for="(tab, idx) in displayTabs"
|
||||
:key="idx"
|
||||
class="comb-tab-item"
|
||||
:class="{ active: activeTabIndex === idx }"
|
||||
:style="{
|
||||
color:
|
||||
activeTabIndex === idx
|
||||
? res.tabActiveColor || '#E93323'
|
||||
: res.tabColor || '#FFFFFF',
|
||||
}"
|
||||
@click="onTabTap(tab, idx)"
|
||||
>
|
||||
<text>{{ tab.title || "未命名" }}</text>
|
||||
<view
|
||||
v-if="activeTabIndex === idx"
|
||||
class="comb-tab-line"
|
||||
:style="{ background: res.tabActiveColor || '#E93323' }"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="comb-tab-toggle" @click="showMoreTabs = !showMoreTabs">
|
||||
<u-icon
|
||||
:name="showMoreTabs ? 'arrow-up' : 'arrow-down'"
|
||||
:color="res.tabColor || '#FFFFFF'"
|
||||
size="28rpx"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下拉展开全部分类面板 -->
|
||||
<view v-if="showMoreTabs" class="comb-tabs-dropdown">
|
||||
<view
|
||||
v-for="(tab, idx) in displayTabs"
|
||||
:key="idx"
|
||||
class="dropdown-chip"
|
||||
:class="{ active: activeTabIndex === idx }"
|
||||
:style="
|
||||
activeTabIndex === idx
|
||||
? {
|
||||
color: res.tabActiveColor || '#E93323',
|
||||
borderColor: res.tabActiveColor || '#E93323',
|
||||
}
|
||||
: {}
|
||||
"
|
||||
@click="onTabTap(tab, idx)"
|
||||
>
|
||||
{{ tab.title || "未命名" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 轮播图区域 -->
|
||||
<view
|
||||
v-if="bannerList.length"
|
||||
class="comb-banner"
|
||||
:class="{ 'no-tabs': res.showTabs === false }"
|
||||
>
|
||||
<swiper
|
||||
class="comb-swiper"
|
||||
autoplay
|
||||
:interval="4500"
|
||||
:duration="500"
|
||||
circular
|
||||
:previous-margin="cardMode ? '48rpx' : '0rpx'"
|
||||
:next-margin="cardMode ? '48rpx' : '0rpx'"
|
||||
@change="onSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="(item, i) in bannerList" :key="i">
|
||||
<view
|
||||
class="comb-slide-item"
|
||||
:class="{ 'is-card': cardMode, active: i === currentSlide }"
|
||||
@click="onBannerTap(item)"
|
||||
>
|
||||
<image
|
||||
class="comb-slide-img"
|
||||
:style="bannerImgStyle"
|
||||
:src="item.img"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 指示器 -->
|
||||
<view
|
||||
v-if="res.indicatorStyle !== 'none' && bannerList.length > 1"
|
||||
class="comb-indicator"
|
||||
:class="'pos-' + (res.indicatorPosition || 'center')"
|
||||
>
|
||||
<view
|
||||
v-for="(item, i) in bannerList"
|
||||
:key="i"
|
||||
class="dot"
|
||||
:class="[res.indicatorStyle || 'round', { active: i === currentSlide }]"
|
||||
:style="
|
||||
i === currentSlide
|
||||
? { background: res.indicatorColor || '#E93323' }
|
||||
: {}
|
||||
"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const toRpx = (v: any) => `${Number(v) || 0}rpx`;
|
||||
|
||||
// 状态栏与胶囊避让
|
||||
const statusBarHeight = ref(0);
|
||||
const capsuleRightPad = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
const info = uni.getWindowInfo();
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
statusBarHeight.value = info.statusBarHeight || 0;
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
const menu = uni.getMenuButtonBoundingClientRect();
|
||||
capsuleRightPad.value = Math.max(0, info.windowWidth - menu.left + 8);
|
||||
// #endif
|
||||
} catch (e) {
|
||||
statusBarHeight.value = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索热词轮换
|
||||
const hotIndex = ref(0);
|
||||
let hotTimer: ReturnType<typeof setInterval> | null = null;
|
||||
const validHotWords = computed(() =>
|
||||
(props.res?.hotWords || []).filter((w: string) => w && String(w).trim())
|
||||
);
|
||||
const displayHotWord = computed(() => {
|
||||
if (validHotWords.value.length) {
|
||||
return validHotWords.value[hotIndex.value % validHotWords.value.length];
|
||||
}
|
||||
return props.res?.placeWords || "搜索商品名称";
|
||||
});
|
||||
|
||||
function stopHotTimer() {
|
||||
if (hotTimer) {
|
||||
clearInterval(hotTimer);
|
||||
hotTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [validHotWords.value.length, props.res?.hotInterval],
|
||||
() => {
|
||||
stopHotTimer();
|
||||
hotIndex.value = 0;
|
||||
if (validHotWords.value.length > 1) {
|
||||
hotTimer = setInterval(() => {
|
||||
hotIndex.value = (hotIndex.value + 1) % validHotWords.value.length;
|
||||
}, (Number(props.res?.hotInterval) || 3) * 1000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(stopHotTimer);
|
||||
|
||||
// 选项卡
|
||||
const activeTabIndex = ref(0);
|
||||
const showMoreTabs = ref(false);
|
||||
const displayTabs = computed(() => {
|
||||
const custom = Array.isArray(props.res?.tabList) ? props.res.tabList : [];
|
||||
return [{ title: "首页", isHome: true }, ...custom];
|
||||
});
|
||||
|
||||
function onTabTap(tab: any, idx: number) {
|
||||
activeTabIndex.value = idx;
|
||||
showMoreTabs.value = false;
|
||||
if (tab.isHome || !tab.value) return;
|
||||
if (tab.dataType === "special") {
|
||||
uni.navigateTo({
|
||||
url: `/pages/tabbar/special/special?id=${tab.value}`,
|
||||
});
|
||||
} else if (tab.dataType === "category") {
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage?category=${tab.value}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 轮播图
|
||||
const currentSlide = ref(0);
|
||||
const bannerList = computed(() =>
|
||||
Array.isArray(props.res?.list) ? props.res.list : []
|
||||
);
|
||||
const cardMode = computed(() => (props.res?.swiperStyle || "card") === "card");
|
||||
const currentBannerImg = computed(() => {
|
||||
const list = bannerList.value;
|
||||
if (!list.length) return "";
|
||||
const item = list[currentSlide.value % list.length] || list[0];
|
||||
return item?.img || "";
|
||||
});
|
||||
|
||||
function onSwiperChange(e: any) {
|
||||
currentSlide.value = e?.detail?.current ?? 0;
|
||||
}
|
||||
|
||||
function onBannerTap(item: any) {
|
||||
if (item) modelNavigateTo(item);
|
||||
}
|
||||
|
||||
function onSearchTap() {
|
||||
const keyword = validHotWords.value.length ? displayHotWord.value : "";
|
||||
const query = keyword ? `?keyword=${encodeURIComponent(keyword)}` : "";
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage${query}`,
|
||||
});
|
||||
}
|
||||
|
||||
// 样式计算
|
||||
const wrapperStyle = computed(() => ({
|
||||
marginBottom: toRpx(props.res?.pageGap ?? 0),
|
||||
}));
|
||||
|
||||
const searchRowStyle = computed(() =>
|
||||
capsuleRightPad.value ? { paddingRight: `${capsuleRightPad.value}px` } : {}
|
||||
);
|
||||
|
||||
const searchBoxStyle = computed(() => {
|
||||
const r = props.res || {};
|
||||
const align = r.textAlign || "left";
|
||||
return {
|
||||
background: r.boxColor || "#FFFFFF",
|
||||
color: r.textColor || "#303133",
|
||||
borderRadius: toRpx(r.searchRadius ?? 30),
|
||||
justifyContent:
|
||||
align === "center"
|
||||
? "center"
|
||||
: align === "right"
|
||||
? "flex-end"
|
||||
: "flex-start",
|
||||
};
|
||||
});
|
||||
|
||||
const bannerImgStyle = computed(() => ({
|
||||
borderRadius: toRpx(props.res?.bannerRadius ?? 14),
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tpl-home-comb {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 16rpx 20rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.comb-bg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, #e93323 0%, #f5f5f5 100%);
|
||||
.comb-bg-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: scale(1.25);
|
||||
filter: blur(40rpx);
|
||||
display: block;
|
||||
}
|
||||
.comb-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 180rpx;
|
||||
background: linear-gradient(180deg, rgba(245, 245, 245, 0) 0%, #f5f5f5 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.comb-search-row {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.comb-logo {
|
||||
width: 138rpx;
|
||||
height: 48rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.comb-search-box {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 60rpx;
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 24rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comb-search-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.comb-tabs-wrap {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.comb-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 72rpx;
|
||||
}
|
||||
|
||||
.comb-tabs-scroll {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.comb-tabs-inner {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.comb-tab-item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 68rpx;
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
&.active {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.comb-tab-line {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 4rpx;
|
||||
transform: translateX(-50%);
|
||||
width: 36rpx;
|
||||
height: 4rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.comb-tab-toggle {
|
||||
flex-shrink: 0;
|
||||
padding-left: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comb-tabs-dropdown {
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx;
|
||||
margin-top: 8rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dropdown-chip {
|
||||
padding: 8rpx 24rpx;
|
||||
background: #f5f5f5;
|
||||
border: 2rpx solid transparent;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.comb-banner {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-top: 12rpx;
|
||||
height: 300rpx;
|
||||
&.no-tabs {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.comb-swiper {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.comb-slide-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
&.is-card {
|
||||
padding: 0 8rpx;
|
||||
transform: scale(0.92);
|
||||
transition: transform 0.35s ease;
|
||||
&.active {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comb-slide-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comb-indicator {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 3;
|
||||
&.pos-left {
|
||||
left: 32rpx;
|
||||
}
|
||||
&.pos-center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
&.pos-right {
|
||||
right: 32rpx;
|
||||
}
|
||||
.dot {
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
transition: all 0.25s ease;
|
||||
&.round {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
&.line {
|
||||
width: 24rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 4rpx;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,54 +1,188 @@
|
||||
<template>
|
||||
<div class="layout" v-if="goodsList.length">
|
||||
<div class="join-list">
|
||||
<div class="join-title" @click="goMore">
|
||||
<div class="join-title-name">{{ res.list[0].title }}</div>
|
||||
<div class="join-title-more">更多</div>
|
||||
</div>
|
||||
<div class="join-box">
|
||||
<div
|
||||
class="join-item"
|
||||
@click="goDetail(item)"
|
||||
v-for="(item, index) in goodsList"
|
||||
:key="index"
|
||||
>
|
||||
<div class="item-img-box">
|
||||
<u-image
|
||||
class="item-img"
|
||||
width="150rpx"
|
||||
height="150rpx"
|
||||
:src="item.goodsImage || item.thumbnail"
|
||||
/>
|
||||
</div>
|
||||
<div class="item-price">
|
||||
<span class="price-text">{{ item.points != null ? item.points : 0 }}积分</span>
|
||||
</div>
|
||||
<div class="item-line-through" v-if="item.originalPrice">
|
||||
<span class="price-text">¥{{ item.originalPrice }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<view class="layout" :style="wrapperStyle" v-if="goodsList.length">
|
||||
<view class="integral-head" :style="headStyle" @click="goMore">
|
||||
<image
|
||||
v-if="styled && options.titleType === 'image' && options.titleImage"
|
||||
class="integral-head-img"
|
||||
:src="options.titleImage"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view v-else class="integral-head-title" :style="headTitleStyle">{{ title }}</view>
|
||||
<view class="integral-head-space"></view>
|
||||
<view class="integral-head-more" :style="moreStyle">更多 ></view>
|
||||
</view>
|
||||
<!-- 老数据:原始四列样式 -->
|
||||
<view v-if="!styled" class="join-box">
|
||||
<view
|
||||
class="join-item"
|
||||
@click="goDetail(item)"
|
||||
v-for="(item, index) in goodsList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="item-img-box">
|
||||
<u-image
|
||||
class="item-img"
|
||||
width="150rpx"
|
||||
height="150rpx"
|
||||
:src="item.goodsImage || item.thumbnail"
|
||||
/>
|
||||
</view>
|
||||
<view class="item-price">
|
||||
<span class="price-text">{{ item.points != null ? item.points : 0 }}积分</span>
|
||||
</view>
|
||||
<view class="item-line-through" v-if="item.originalPrice">
|
||||
<span class="price-text">¥{{ item.originalPrice }}</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 样式一:两列大卡片 -->
|
||||
<view v-else-if="layoutStyle === '1'" class="ig-grid2" :style="gapStyle">
|
||||
<view
|
||||
class="ig-g2-item"
|
||||
v-for="(item, index) in goodsList"
|
||||
:key="index"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<view class="ig-g2-img-box" :style="imgRadiusStyle">
|
||||
<image class="ig-img" :src="item.goodsImage || item.thumbnail" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="ig-g2-name" :style="nameStyle">{{ item.goodsName || item.promotionName || '' }}</view>
|
||||
<view class="ig-price-row">
|
||||
<view class="ig-price" :style="pointStyle">
|
||||
<text class="ig-coin">¥</text>{{ item.points != null ? item.points : 0 }}积分
|
||||
</view>
|
||||
<text class="ig-original" v-if="item.originalPrice">¥{{ item.originalPrice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 样式二:三列,积分价在名称上方 -->
|
||||
<view v-else-if="layoutStyle === '2'" class="ig-grid3" :style="gapStyle">
|
||||
<view
|
||||
class="ig-g3-item"
|
||||
v-for="(item, index) in goodsList"
|
||||
:key="index"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<view class="ig-g3-img-box" :style="imgRadiusStyle">
|
||||
<image class="ig-img" :src="item.goodsImage || item.thumbnail" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="ig-price ig-price-sm" :style="pointStyle">
|
||||
<text class="ig-coin ig-coin-sm">¥</text>{{ item.points != null ? item.points : 0 }}积分
|
||||
</view>
|
||||
<view class="ig-g3-name" :style="nameStyle">{{ item.goodsName || item.promotionName || '' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 样式三:三列,仅积分价 -->
|
||||
<view v-else class="ig-grid3" :style="gapStyle">
|
||||
<view
|
||||
class="ig-g3-item"
|
||||
v-for="(item, index) in goodsList"
|
||||
:key="index"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<view class="ig-g3-img-box" :style="imgRadiusStyle">
|
||||
<image class="ig-img" :src="item.goodsImage || item.thumbnail" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="ig-price ig-price-sm" :style="pointStyle">
|
||||
<text class="ig-coin ig-coin-sm">¥</text>{{ item.points != null ? item.points : 0 }}积分
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import * as API_Promotions from "@/api/promotions";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const goodsList = ref<any[]>([]);
|
||||
|
||||
const options = computed(() => props.res || {});
|
||||
const title = computed(() => props.res?.list?.[0]?.title || "积分兑好礼");
|
||||
const count = computed(() => props.res?.list?.[0]?.count || 6);
|
||||
const layoutStyle = computed(() => props.res?.layoutStyle || "1");
|
||||
|
||||
// 面板保存过样式字段才启用新样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (styled.value) {
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
const radius = Number(res.borderRadius) || 0;
|
||||
return {
|
||||
background: bg,
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
borderRadius: `${radius}rpx`,
|
||||
overflow: radius > 0 ? "hidden" : "visible",
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: res.bgColor || "#FFFFFF",
|
||||
padding: `0 ${res.paddingX ?? 0}rpx`,
|
||||
marginBottom: `${res.pageGap ?? 10}rpx`,
|
||||
borderRadius: `${res.borderRadius ?? 0}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
});
|
||||
|
||||
const headStyle = computed(() => {
|
||||
if (!styled.value) return {};
|
||||
const res = props.res;
|
||||
if (res.headBgType === "image" && res.headBgImage) {
|
||||
return {
|
||||
backgroundImage: `url(${res.headBgImage})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
};
|
||||
}
|
||||
const bg =
|
||||
!res.headBg2 || res.headBg1 === res.headBg2
|
||||
? res.headBg1
|
||||
: `linear-gradient(90deg, ${res.headBg1}, ${res.headBg2})`;
|
||||
return { background: bg };
|
||||
});
|
||||
|
||||
const headTitleStyle = computed(() => {
|
||||
if (!styled.value) return {};
|
||||
const res = props.res;
|
||||
return {
|
||||
color: res.titleColor || "#FFFFFF",
|
||||
fontWeight: res.titleStyle === "bold" ? "bold" : "normal",
|
||||
fontStyle: res.titleStyle === "italic" ? "italic" : "normal",
|
||||
};
|
||||
});
|
||||
|
||||
const moreStyle = computed(() =>
|
||||
styled.value ? { color: props.res.moreColor || "#999999" } : {}
|
||||
);
|
||||
const pointStyle = computed(() =>
|
||||
styled.value && props.res.pointColor ? { color: props.res.pointColor } : {}
|
||||
);
|
||||
const nameStyle = computed(() =>
|
||||
styled.value && props.res.nameColor ? { color: props.res.nameColor } : {}
|
||||
);
|
||||
const gapStyle = computed(() => {
|
||||
if (!styled.value) return {};
|
||||
const gap = `${Number(props.res.contentGap ?? 10) || 0}rpx`;
|
||||
return { gap, "--ig-gap": gap };
|
||||
});
|
||||
const imgRadiusStyle = computed(() =>
|
||||
styled.value ? { borderRadius: `${Number(props.res.imgRadius) || 0}rpx`, overflow: "hidden" } : {}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
API_Promotions.getPointsGoods({
|
||||
pageNumber: 1,
|
||||
pageSize: props.res.list[0].count || 3,
|
||||
pageSize: count.value,
|
||||
}).then((res) => {
|
||||
if (res.data.success && res.data.result && res.data.result.records) {
|
||||
goodsList.value = res.data.result.records.slice(
|
||||
0,
|
||||
props.res.list[0].count || 3
|
||||
);
|
||||
goodsList.value = res.data.result.records.slice(0, count.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -63,12 +197,39 @@ function goDetail(item: any) {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
.integral-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 96rpx;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.integral-head-img {
|
||||
width: 154rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
.integral-head-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.integral-head-space {
|
||||
flex: 1;
|
||||
}
|
||||
.integral-head-more {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.join-box {
|
||||
display: flex;
|
||||
}
|
||||
.item-price {
|
||||
.price-text {
|
||||
font-size: 15px;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: $main-color;
|
||||
}
|
||||
@@ -77,36 +238,116 @@ function goDetail(item: any) {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.item-img {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
.item-img-box {
|
||||
position: relative;
|
||||
}
|
||||
.item-line-through {
|
||||
.price-text {
|
||||
font-size: 10px;
|
||||
font-size: 20rpx;
|
||||
font-weight: 400;
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.join-title {
|
||||
|
||||
/* 积分商品卡片(参考 CRMEB 积分商城) */
|
||||
.ig-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.ig-grid2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ig-g2-item {
|
||||
width: calc((100% - var(--ig-gap, 10rpx)) / 2);
|
||||
background: #fff;
|
||||
height: 50px;
|
||||
.join-title-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.join-title-more {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
padding-bottom: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ig-g2-img-box {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.ig-g2-name {
|
||||
margin: 16rpx 16rpx 0;
|
||||
font-size: 26rpx;
|
||||
color: #282828;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ig-price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin: 10rpx 16rpx 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.ig-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #f5483a;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ig-price-sm {
|
||||
font-size: 24rpx;
|
||||
margin: 10rpx 16rpx 0;
|
||||
}
|
||||
.ig-coin {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #ffe29a, #f5a623);
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
font-weight: bold;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 6rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ig-coin-sm {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
font-size: 16rpx;
|
||||
}
|
||||
.ig-original {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
white-space: nowrap;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.ig-grid3 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ig-g3-item {
|
||||
width: calc((100% - var(--ig-gap, 10rpx) * 2) / 3);
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
padding-bottom: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ig-g3-img-box {
|
||||
width: 100%;
|
||||
height: 184rpx;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.ig-g3-name {
|
||||
margin: 8rpx 16rpx 0;
|
||||
font-size: 24rpx;
|
||||
color: #282828;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
|
||||
<template>
|
||||
|
||||
<view class="layout">
|
||||
<view class="view-height-150" @click="modelNavigateTo(res.list[0])">
|
||||
<view class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<view class="view-height-150" :style="imgStyle" @click="modelNavigateTo(res.list[0])">
|
||||
<u-image width="100%" height="340rpx" class="image-mode" :src="res.list[0].img">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="view-height-150">
|
||||
<view class="view-height-75" @click="modelNavigateTo(res.list[1])">
|
||||
<view class="view-height-75" :style="imgStyle" @click="modelNavigateTo(res.list[1])">
|
||||
<u-image width="100%" height="170rpx" class="image-mode" :src="res.list[1].img" alt>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="view-height-75" @click="modelNavigateTo(res.list[2])">
|
||||
<view class="view-height-75" :style="imgStyle" @click="modelNavigateTo(res.list[2])">
|
||||
<u-image width="100%" height="170rpx" class="image-mode" :src="res.list[2].img" alt>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
@@ -22,9 +22,14 @@
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -35,4 +40,7 @@ defineProps<{ res: any }>();
|
||||
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
.view-height-150 {
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
|
||||
<template>
|
||||
<div class="layout ">
|
||||
<div class="layout " :style="[wrapperStyle, gapStyle]">
|
||||
<div class="view-height-150">
|
||||
<div class="view-height-75" @click="modelNavigateTo(res.list[0])">
|
||||
<div class="view-height-75" :style="imgStyle" @click="modelNavigateTo(res.list[0])">
|
||||
<u-image class="image-mode" width="100%" height="150rpx" :src="res.list[0].img" ></u-image>
|
||||
</div>
|
||||
<div class="view-height-75" @click="modelNavigateTo(res.list[1])">
|
||||
<div class="view-height-75" :style="imgStyle" @click="modelNavigateTo(res.list[1])">
|
||||
<u-image class="image-mode" width="100%" height="150rpx" :src="res.list[1].img" ></u-image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-height-150" @click="modelNavigateTo(res.list[2])">
|
||||
<div class="view-height-150" :style="imgStyle" @click="modelNavigateTo(res.list[2])">
|
||||
<u-image class="image-mode" width="100%" height="300rpx" :src="res.list[2].img" ></u-image>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -30,5 +35,8 @@ defineProps<{ res: any }>();
|
||||
|
||||
background-size: cover;
|
||||
}
|
||||
.view-height-150 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
30
pages/tabbar/home/template/tpl_line.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<view class="tpl-line" :style="wrapperStyle">
|
||||
<view :style="lineStyle"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
const bg =
|
||||
res.bgColor1 !== undefined
|
||||
? !res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`
|
||||
: res.bgColor || "#FFFFFF";
|
||||
return {
|
||||
background: bg,
|
||||
padding: `0 ${res.paddingX ?? 0}rpx`,
|
||||
marginBottom: `${res.pageGap ?? 10}rpx`,
|
||||
};
|
||||
});
|
||||
|
||||
const lineStyle = computed(() => ({
|
||||
borderTop: `${props.res?.lineHeight ?? 1}rpx ${props.res?.lineStyle || "dashed"} ${props.res?.lineColor || "#979797"}`,
|
||||
}));
|
||||
</script>
|
||||
@@ -1,31 +1,61 @@
|
||||
<template>
|
||||
<view class="layout">
|
||||
<view class="menu-list">
|
||||
<view class="layout" :style="wrapperStyle">
|
||||
<view
|
||||
class="menu-list"
|
||||
:class="{ styled: styled, single: isSingle }"
|
||||
:style="listStyle"
|
||||
>
|
||||
<view
|
||||
class="menu-item"
|
||||
:style="itemStyle"
|
||||
@click="modelNavigateTo(item)"
|
||||
v-for="(item, index) in res.list"
|
||||
v-for="(item, index) in visibleList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="menu-img-wrap">
|
||||
<u-image
|
||||
width="88rpx"
|
||||
height="88rpx"
|
||||
class="menu-img"
|
||||
:src="item.img"
|
||||
>
|
||||
<view class="menu-img-wrap" :style="imgStyle">
|
||||
<u-image width="88rpx" height="88rpx" class="menu-img" :src="item.img">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="menu-title">{{ item.title }}</view>
|
||||
<view class="menu-title" :style="titleStyle">{{ item.title }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
// 面板保存过样式字段才启用自定义样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const listStyle = computed(() =>
|
||||
styled.value ? { rowGap: `${Number(props.res.contentGap) || 0}rpx` } : {}
|
||||
);
|
||||
const titleStyle = computed(() =>
|
||||
styled.value && props.res.textColor ? { color: props.res.textColor } : {}
|
||||
);
|
||||
|
||||
// 展示设置:单行/多行、行数、每行个数,超出部分不展示
|
||||
const isSingle = computed(
|
||||
() => styled.value && props.res.menuDisplay === "single"
|
||||
);
|
||||
const cols = computed(() =>
|
||||
styled.value ? Number(props.res.menuCols) || 4 : 4
|
||||
);
|
||||
const rows = computed(() => Number(props.res.menuRows) || 3);
|
||||
const itemStyle = computed(() =>
|
||||
styled.value ? { flex: `0 0 ${100 / cols.value}%` } : {}
|
||||
);
|
||||
const visibleList = computed(() => {
|
||||
const list = props.res.list || [];
|
||||
if (!styled.value) return list;
|
||||
const limit = isSingle.value ? cols.value : rows.value * cols.value;
|
||||
return list.slice(0, limit);
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -34,17 +64,28 @@ defineProps<{ res: any }>();
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
|
||||
&.single {
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .menu-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 20%;
|
||||
flex: 0 0 20%;
|
||||
width: 25%;
|
||||
flex: 0 0 25%;
|
||||
box-sizing: border-box;
|
||||
margin: 20rpx 0;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
&.styled > .menu-item,
|
||||
&.single > .menu-item {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-img-wrap {
|
||||
@@ -72,4 +113,4 @@ defineProps<{ res: any }>();
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,55 +1,103 @@
|
||||
<template>
|
||||
<div class="layout" v-if="list.length">
|
||||
<div class="background">
|
||||
<u-notice-bar
|
||||
direction="column"
|
||||
:bg-color="noticeStyle.bk_color"
|
||||
:color="noticeStyle.color"
|
||||
:text="list"
|
||||
></u-notice-bar>
|
||||
</div>
|
||||
</div>
|
||||
<view class="layout" :class="{ styled: styled }" v-if="texts.length" :style="wrapperStyle">
|
||||
<view class="notice-bar">
|
||||
<image
|
||||
v-if="styled && item.img"
|
||||
class="notice-icon"
|
||||
:src="item.img"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="notice-body" :style="{ textAlign: alignValue }">
|
||||
<u-notice-bar
|
||||
:direction="directionValue"
|
||||
:bg-color="styled ? 'transparent' : item.bk_color"
|
||||
:color="item.color"
|
||||
:text="texts"
|
||||
@click="clickNotice"
|
||||
></u-notice-bar>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const noticeStyle = computed(() => props.res?.list?.[0] || {});
|
||||
const item = computed(() => props.res?.list?.[0] || {});
|
||||
|
||||
const list = computed(() => {
|
||||
const titles = props.res?.list?.[0]?.title;
|
||||
if (!Array.isArray(titles)) return [];
|
||||
return titles
|
||||
.map((item: { context?: string }) => (item?.context == null ? "" : String(item.context)))
|
||||
.filter((text: string) => text.length > 0);
|
||||
// 面板保存过样式字段才启用自定义样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingX !== undefined);
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (!styled.value) return {};
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
const radius = Number(res.borderRadius) || 0;
|
||||
return {
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
background: bg,
|
||||
borderRadius: `${radius}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
overflow: radius > 0 ? "hidden" : "visible",
|
||||
};
|
||||
});
|
||||
|
||||
const directionValue = computed(() =>
|
||||
styled.value && item.value.direction === "horizontal" ? "row" : "column"
|
||||
);
|
||||
const alignValue = computed(() =>
|
||||
styled.value ? item.value.textAlign || "left" : "left"
|
||||
);
|
||||
|
||||
const titleList = computed(() => {
|
||||
const titles = item.value?.title;
|
||||
if (!Array.isArray(titles)) return [];
|
||||
return titles.filter(
|
||||
(t: any) => t && t.context != null && String(t.context).length > 0
|
||||
);
|
||||
});
|
||||
|
||||
const texts = computed(() =>
|
||||
titleList.value.map((t: any) => String(t.context))
|
||||
);
|
||||
|
||||
function clickNotice(index: number) {
|
||||
const target = titleList.value[index];
|
||||
if (target && target.url) {
|
||||
modelNavigateTo(target);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
.background {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
text-align: left;
|
||||
font-size: 20rpx;
|
||||
background-size: cover;
|
||||
}
|
||||
.layout {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 84rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: #ffffff;
|
||||
}
|
||||
.title {
|
||||
line-height: 84rpx;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
@import "./tpl.scss";
|
||||
.layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 84rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
.layout.styled {
|
||||
height: auto;
|
||||
}
|
||||
.notice-bar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.notice-icon {
|
||||
width: 130rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
.notice-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
35
pages/tabbar/home/template/tpl_rich_text.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<view class="tpl-rich-text" :style="wrapperStyle">
|
||||
<rich-text :nodes="processedHtml"></rich-text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
const bg =
|
||||
res.bgColor1 !== undefined
|
||||
? !res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`
|
||||
: res.bgColor || "#FFFFFF";
|
||||
return {
|
||||
background: bg,
|
||||
padding: `0 ${res.paddingX ?? 12}rpx`,
|
||||
marginBottom: `${res.pageGap ?? 10}rpx`,
|
||||
borderRadius: `${res.borderRadius ?? 0}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
});
|
||||
|
||||
// 防止富文本内图片超宽撑破布局
|
||||
const processedHtml = computed(() => {
|
||||
const html = props.res?.content;
|
||||
if (!html) return "";
|
||||
return String(html).replace(/<img/gi, '<img style="max-width:100%;height:auto;"');
|
||||
});
|
||||
</script>
|
||||
@@ -1,57 +1,123 @@
|
||||
<template>
|
||||
<view class="layout">
|
||||
<view class="search" @click="handleSearch">
|
||||
<u-icon name="search"></u-icon>
|
||||
<text class="search-text">{{ res.list[0].title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ res: any; storeId?: string }>();
|
||||
|
||||
function handleSearch() {
|
||||
if (props.storeId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage?storeId=${props.storeId}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage`,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
|
||||
.search {
|
||||
width: 100%;
|
||||
height: 64rpx;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0 10rpx;
|
||||
background: #ededed;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-text {
|
||||
margin-left: 12rpx;
|
||||
font-size: 26rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="layout" :style="wrapperStyle">
|
||||
<view class="search" :style="boxStyle" @click="handleSearch">
|
||||
<u-icon name="search"></u-icon>
|
||||
<text class="search-text">{{ displayText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, onBeforeUnmount } from "vue";
|
||||
|
||||
const props = defineProps<{ res: any; storeId?: string }>();
|
||||
|
||||
// 面板保存过样式字段才启用自定义样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingX !== undefined);
|
||||
|
||||
// 搜索热词轮播:有热词时按切换时间轮换展示,否则展示提示文字
|
||||
const hotIndex = ref(0);
|
||||
let hotTimer: ReturnType<typeof setInterval> | null = null;
|
||||
const validWords = computed(() =>
|
||||
(props.res.hotWords || []).filter((w: string) => w && w.trim())
|
||||
);
|
||||
const displayText = computed(() => {
|
||||
if (validWords.value.length) {
|
||||
return validWords.value[hotIndex.value % validWords.value.length];
|
||||
}
|
||||
return props.res.list?.[0]?.title || "";
|
||||
});
|
||||
|
||||
function stopHot() {
|
||||
if (hotTimer) {
|
||||
clearInterval(hotTimer);
|
||||
hotTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [validWords.value.length, props.res.hotInterval],
|
||||
() => {
|
||||
stopHot();
|
||||
hotIndex.value = 0;
|
||||
if (validWords.value.length > 1) {
|
||||
hotTimer = setInterval(() => {
|
||||
hotIndex.value = (hotIndex.value + 1) % validWords.value.length;
|
||||
}, (Number(props.res.hotInterval) || 3) * 1000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(stopHot);
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (!styled.value) return {};
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
return {
|
||||
padding: `0 ${Number(res.paddingX) || 0}rpx`,
|
||||
background: bg,
|
||||
borderRadius: `${Number(res.borderRadius) || 0}rpx`,
|
||||
overflow: Number(res.borderRadius) > 0 ? "hidden" : "visible",
|
||||
};
|
||||
});
|
||||
const boxStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (!styled.value) return {};
|
||||
return {
|
||||
background: res.boxColor || "#EDEDED",
|
||||
color: res.textColor || "#999999",
|
||||
borderRadius: `${Number(res.boxRadius) || 0}rpx`,
|
||||
justifyContent: res.textAlign === "center" ? "center" : "flex-start",
|
||||
};
|
||||
});
|
||||
|
||||
function handleSearch() {
|
||||
if (props.storeId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage?storeId=${props.storeId}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage`,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
|
||||
.search {
|
||||
width: 100%;
|
||||
height: 64rpx;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0 10rpx;
|
||||
background: #ededed;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-text {
|
||||
margin-left: 12rpx;
|
||||
font-size: 26rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
378
pages/tabbar/home/template/tpl_shop.vue
Normal file
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<view class="tpl-shop" :style="wrapperStyle" v-if="shopList.length">
|
||||
<view class="shop-head" v-if="headIcon || title || moreUrl">
|
||||
<image v-if="headIcon" class="shop-head-icon" :src="headIcon" mode="aspectFit" />
|
||||
<view class="shop-title" :style="titleStyle">{{ title }}</view>
|
||||
<view v-if="moreUrl" class="shop-more" :style="moreStyle" @click="goMore">更多 ></view>
|
||||
</view>
|
||||
|
||||
<!-- 老数据:两列网格 -->
|
||||
<view v-if="!styled" class="shop-grid-legacy">
|
||||
<view
|
||||
class="shop-item"
|
||||
v-for="item in shopList"
|
||||
:key="item.id"
|
||||
@click="goShop(item)"
|
||||
>
|
||||
<image
|
||||
v-if="showLogo"
|
||||
class="shop-logo"
|
||||
:src="item.storeLogo"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-if="showName" class="shop-name">{{ item.storeName }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 样式一:店铺卡片(商品图背景 + Logo + 名称 + 类型标签) -->
|
||||
<view v-else-if="shopStyle === 'one'" class="shop-grid">
|
||||
<view
|
||||
class="shop-card"
|
||||
v-for="item in shopList"
|
||||
:key="item.id"
|
||||
@click="goShop(item)"
|
||||
>
|
||||
<image
|
||||
v-if="(item.goodsImages && item.goodsImages.length) || item.storeBanner"
|
||||
class="shop-card-bg"
|
||||
:style="logoStyle"
|
||||
:src="item.goodsImages && item.goodsImages.length ? item.goodsImages[0] : item.storeBanner"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="shop-card-bg shop-card-bg-empty" :style="logoStyle"></view>
|
||||
<view class="shop-card-body">
|
||||
<image
|
||||
v-if="showLogo"
|
||||
class="shop-card-logo"
|
||||
:src="item.storeLogo"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view v-if="showName" class="shop-card-name" :style="nameStyle">{{ item.storeName }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 样式二:店铺卡片 + 商品列表 -->
|
||||
<view v-else class="shop-street-list">
|
||||
<view
|
||||
class="shop-street-card"
|
||||
v-for="item in shopList"
|
||||
:key="item.id"
|
||||
@click="goShop(item)"
|
||||
>
|
||||
<view class="shop-street-head">
|
||||
<image v-if="showLogo" class="shop-street-logo" :src="item.storeLogo" mode="aspectFit" />
|
||||
<view v-if="showName" class="shop-street-name" :style="nameStyle">{{ item.storeName }}</view>
|
||||
<image
|
||||
v-if="(item.goods && item.goods.length) || item.storeBanner"
|
||||
class="shop-street-banner"
|
||||
:style="logoStyle"
|
||||
:src="item.goods && item.goods.length ? item.goods[0].img : item.storeBanner"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="shop-street-goods">
|
||||
<view
|
||||
class="street-goods-item"
|
||||
v-for="(g, gi) in item.goods || []"
|
||||
:key="gi"
|
||||
>
|
||||
<image class="street-goods-img" :src="g.img" mode="aspectFill" />
|
||||
<view class="street-goods-name">{{ g.name }}</view>
|
||||
<view class="street-goods-price">
|
||||
¥{{ goodsFormatPrice(g.price)[0] }}.{{ goodsFormatPrice(g.price)[1] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { getStoreList } from "@/api/store.js";
|
||||
import { getGoodsList } from "@/api/goods.js";
|
||||
import { goodsFormatPrice } from "@/utils/filters.js";
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const shopList = ref<any[]>([]);
|
||||
|
||||
const first = computed(() => props.res?.list?.[0] || {});
|
||||
const title = computed(() => first.value.title || "");
|
||||
const count = computed(() => first.value.count || 4);
|
||||
const headIcon = computed(() => first.value.icon || "");
|
||||
const moreUrl = computed(() => first.value.moreUrl || "");
|
||||
const showLogo = computed(() => first.value.showLogo !== false);
|
||||
const showName = computed(() => first.value.showName !== false);
|
||||
const shopStyle = computed(() => first.value.shopStyle || "one");
|
||||
|
||||
// 面板保存过样式字段才启用自定义样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (styled.value) {
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
const radius = Number(res.borderRadius) || 0;
|
||||
return {
|
||||
background: bg,
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
borderRadius: `${radius}rpx`,
|
||||
overflow: radius > 0 ? "hidden" : "visible",
|
||||
};
|
||||
}
|
||||
return {
|
||||
background: res.bgColor || "#FFFFFF",
|
||||
padding: `20rpx ${res.paddingX ?? 0}rpx`,
|
||||
marginBottom: `${res.pageGap ?? 10}rpx`,
|
||||
borderRadius: `${res.borderRadius ?? 0}rpx`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
});
|
||||
const titleStyle = computed(() =>
|
||||
styled.value && props.res.titleColor ? { color: props.res.titleColor } : {}
|
||||
);
|
||||
const nameStyle = computed(() =>
|
||||
styled.value && props.res.nameColor ? { color: props.res.nameColor } : {}
|
||||
);
|
||||
const moreStyle = computed(() =>
|
||||
styled.value && props.res.moreColor ? { color: props.res.moreColor } : {}
|
||||
);
|
||||
const logoStyle = computed(() =>
|
||||
styled.value ? { borderRadius: `${Number(props.res.imgRadius) || 0}rpx` } : {}
|
||||
);
|
||||
|
||||
async function loadShops() {
|
||||
const item = first.value;
|
||||
if (item.dataMode === "custom" && Array.isArray(item.shopList) && item.shopList.length) {
|
||||
shopList.value = item.shopList;
|
||||
} else {
|
||||
const res = await getStoreList({ pageNumber: 1, pageSize: count.value });
|
||||
if (res.data.success && res.data.result) {
|
||||
shopList.value = (res.data.result.records || []).slice(0, count.value);
|
||||
}
|
||||
}
|
||||
loadGoodsImages();
|
||||
}
|
||||
|
||||
// 样式二:按店铺拉取商品(图片、名称、价格)
|
||||
function loadGoodsImages() {
|
||||
if (shopStyle.value !== "two") return;
|
||||
shopList.value.forEach((shop: any, i: number) => {
|
||||
getGoodsList({ storeId: shop.id, pageNumber: 1, pageSize: 3 }).then((res: any) => {
|
||||
const records = res?.data?.result?.records || res?.data?.result || [];
|
||||
const goods = (Array.isArray(records) ? records : [])
|
||||
.map((g: any) => ({
|
||||
name: g.goodsName,
|
||||
price: g.price,
|
||||
img: g.thumbnail || g.small || g.goodsImage,
|
||||
}))
|
||||
.filter((g: any) => g.img);
|
||||
shopList.value[i].goods = goods;
|
||||
shopList.value[i].goodsImages = goods.map((g: any) => g.img);
|
||||
}).catch(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [first.value.dataMode, first.value.shopList, count.value],
|
||||
() => loadShops(),
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
function goShop(item: any) {
|
||||
uni.navigateTo({ url: `/pages/product/shopPage?id=${item.id}` });
|
||||
}
|
||||
|
||||
function goMore() {
|
||||
modelNavigateTo({ url: moreUrl.value });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shop-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 24rpx 20rpx;
|
||||
}
|
||||
.shop-head-icon {
|
||||
width: 124rpx;
|
||||
height: 32rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.shop-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.shop-more {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 样式一:三列店铺卡片
|
||||
.shop-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 24rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.shop-card {
|
||||
width: calc((100% - 32rpx) / 3);
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.shop-card-bg {
|
||||
width: 100%;
|
||||
height: 140rpx;
|
||||
display: block;
|
||||
background: #eee;
|
||||
}
|
||||
.shop-card-bg-empty {
|
||||
background: linear-gradient(135deg, #f5f5f5, #e8e8e8);
|
||||
}
|
||||
.shop-card-body {
|
||||
padding: 8rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.shop-card-logo {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 96rpx;
|
||||
height: 32rpx;
|
||||
margin: -16rpx auto 0;
|
||||
background: #fff;
|
||||
}
|
||||
.shop-card-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
// 样式二:店铺卡片 + 商品列表
|
||||
.shop-street-list {
|
||||
padding: 0 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.shop-street-card {
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.shop-street-head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 68rpx;
|
||||
padding: 0 16rpx;
|
||||
box-sizing: border-box;
|
||||
background: var(--theme-primary, #ff3c2a);
|
||||
overflow: hidden;
|
||||
}
|
||||
.shop-street-logo {
|
||||
flex-shrink: 0;
|
||||
width: 88rpx;
|
||||
height: 32rpx;
|
||||
padding: 2rpx 6rpx;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
.shop-street-name {
|
||||
max-width: calc(100% - 220rpx);
|
||||
margin-left: 12rpx;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.shop-street-banner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 160rpx;
|
||||
height: 68rpx;
|
||||
}
|
||||
.shop-street-goods {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
padding: 12rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.street-goods-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.street-goods-img {
|
||||
width: 100%;
|
||||
height: 116rpx;
|
||||
display: block;
|
||||
background: #eee;
|
||||
}
|
||||
.street-goods-name {
|
||||
padding: 8rpx 8rpx 0;
|
||||
color: #282828;
|
||||
font-size: 22rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.street-goods-price {
|
||||
padding: 4rpx 8rpx 10rpx;
|
||||
color: var(--theme-primary, #ff3c2a);
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// 老数据两列网格(未配置样式时)
|
||||
.shop-grid-legacy {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.shop-item {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.shop-logo {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.shop-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-left: 16rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +1,30 @@
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="view-list">
|
||||
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<div class="view-list" :style="gapStyle">
|
||||
<div class="view-item" @click="modelNavigateTo(res.list[0])">
|
||||
<div class="-item-tilte">{{res.list[0].title}}</div>
|
||||
<div class="-item-image">
|
||||
<div class="-item-image" :style="imgStyle">
|
||||
<u-image width="120rpx" mode="scaleToFill" height="120rpx" :src="res.list[0].img" alt ></u-image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-item" @click="modelNavigateTo(res.list[1])">
|
||||
<div class="-item-tilte">{{res.list[1].title}}</div>
|
||||
<div class="-item-image">
|
||||
<div class="-item-image" :style="imgStyle">
|
||||
<u-image width="120rpx" mode="scaleToFill" height="120rpx" :src="res.list[1].img" alt ></u-image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-list">
|
||||
<div class="view-list" :style="gapStyle">
|
||||
<div class="view-item" @click="modelNavigateTo(res.list[2])">
|
||||
<div class="-item-tilte">{{res.list[2].title}}</div>
|
||||
<div class="-item-image">
|
||||
<div class="-item-image" :style="imgStyle">
|
||||
<u-image width="120rpx" mode="scaleToFill" height="120rpx" :src="res.list[2].img" alt ></u-image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-item" @click="modelNavigateTo(res.list[3])">
|
||||
<div class="-item-tilte">{{res.list[3].title}}</div>
|
||||
<div class="-item-image">
|
||||
<div class="-item-image" :style="imgStyle">
|
||||
<u-image width="120rpx" mode="scaleToFill" height="120rpx" :src="res.list[3].img" alt ></u-image>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,9 +33,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -48,15 +53,15 @@ defineProps<{ res: any }>();
|
||||
padding: 0;
|
||||
}
|
||||
.-item-image {
|
||||
padding: 10px;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.-item-tilte {
|
||||
background: $aider-light-color;
|
||||
height: 60rpx;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
line-height: 60rpx;
|
||||
}
|
||||
|
||||
.view-list {
|
||||
@@ -66,10 +71,11 @@ defineProps<{ res: any }>();
|
||||
background: #fff;
|
||||
border-top-left-radius: 0.8em;
|
||||
border-top-right-radius: 0.8em;
|
||||
border: 1px solid #ededed;
|
||||
|
||||
border: 2rpx solid #ededed;
|
||||
|
||||
> .view-item {
|
||||
width: 50%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
> .view-item:nth-of-type(1) {
|
||||
> .-item-tilte {
|
||||
@@ -82,4 +88,4 @@ defineProps<{ res: any }>();
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,149 @@
|
||||
<template>
|
||||
<div class="layout" :style="{textAlign: res.list[0].textAlign}" @click="modelNavigateTo(res.list[0])" >
|
||||
<div class="background" :style="{ backgroundColor: res.list[0].bk_color}">
|
||||
<div class="title" :style="{ color: res.list[0].color }">
|
||||
{{ res.list[0].title }}
|
||||
</div>
|
||||
<div style="position: absolute;right: 10px;top:2px;color: #fff;line-height: 42px;font-size: 10px">
|
||||
<a :style="{ color: res.list[0].color1 }" style="text-decoration: none">{{ res.list[0].title1 }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
.background {
|
||||
// background: url("/static/title.png") no-repeat;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
background-position-x: center;
|
||||
background-position-y: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.layout {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 84rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 84rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="layout" :style="wrapperStyle">
|
||||
<!-- 新样式:左标题+副标题,右侧更多 -->
|
||||
<view v-if="styled" class="title-bar" :style="barStyle">
|
||||
<view class="title-left">
|
||||
<text class="title-main" :style="mainStyle">{{ item.title }}</text>
|
||||
<text class="title-sub" :style="subStyle" v-if="item.title1">{{ item.title1 }}</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="item.showMore"
|
||||
class="title-more"
|
||||
:style="moreStyle"
|
||||
@click="goMore"
|
||||
>
|
||||
{{ item.moreText || "更多" }} >
|
||||
</view>
|
||||
</view>
|
||||
<!-- 老数据:保持原样 -->
|
||||
<view
|
||||
v-else
|
||||
class="background"
|
||||
:style="{ backgroundColor: item.bk_color, textAlign: item.textAlign }"
|
||||
@click="modelNavigateTo(item)"
|
||||
>
|
||||
<view class="title" :style="{ color: item.color }">{{ item.title }}</view>
|
||||
<view class="legacy-sub">
|
||||
<a :style="{ color: item.color1 }" style="text-decoration: none">{{ item.title1 }}</a>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const item = computed(() => props.res?.list?.[0] || {});
|
||||
|
||||
// 面板保存过样式字段才启用新样式,老数据保持原样
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (!styled.value) return {};
|
||||
return {
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
borderRadius: `${Number(res.radiusTop) || 0}rpx ${Number(res.radiusTop) || 0}rpx ${Number(res.radiusBottom) || 0}rpx ${Number(res.radiusBottom) || 0}rpx`,
|
||||
overflow:
|
||||
Number(res.radiusTop) > 0 || Number(res.radiusBottom) > 0 ? "hidden" : "visible",
|
||||
background: "#ffffff",
|
||||
boxSizing: "border-box",
|
||||
};
|
||||
});
|
||||
|
||||
const barStyle = computed(() => {
|
||||
const it = item.value;
|
||||
if (it.bgType === "image" && it.bgImage) {
|
||||
return {
|
||||
backgroundImage: `url(${it.bgImage})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
};
|
||||
}
|
||||
return { backgroundColor: it.bk_color || "#FFFFFF" };
|
||||
});
|
||||
|
||||
const mainStyle = computed(() => ({
|
||||
color: item.value.color || "#282828",
|
||||
fontSize: `${(Number(item.value.fontSize) || 20) * 2}rpx`,
|
||||
fontWeight: item.value.textStyle === "bold" ? "bold" : "normal",
|
||||
fontStyle: item.value.textStyle === "italic" ? "italic" : "normal",
|
||||
}));
|
||||
const subStyle = computed(() => ({
|
||||
color: item.value.color1 || "#282828",
|
||||
fontSize: `${(Number(item.value.subFontSize) || 14) * 2}rpx`,
|
||||
}));
|
||||
const moreStyle = computed(() => ({
|
||||
color: item.value.moreColor || "#282828",
|
||||
fontSize: `${(Number(item.value.moreFontSize) || 12) * 2}rpx`,
|
||||
}));
|
||||
|
||||
function goMore() {
|
||||
if (item.value.moreUrl) {
|
||||
modelNavigateTo({ url: item.value.moreUrl });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
.layout {
|
||||
background: #ffffff;
|
||||
}
|
||||
.title-bar {
|
||||
width: 100%;
|
||||
min-height: 84rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.title-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
overflow: hidden;
|
||||
}
|
||||
.title-main {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.title-sub {
|
||||
margin-left: 12rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.title-more {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.background {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.legacy-sub {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 4rpx;
|
||||
color: #fff;
|
||||
line-height: 84rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
.title {
|
||||
line-height: 84rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="view-width-100" @click="modelNavigateTo(res.list[0])">
|
||||
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<div class="view-width-100" :style="imgStyle" @click="modelNavigateTo(res.list[0])">
|
||||
<u-image class="image-mode" width="100%" height="200rpx" :src="res.list[0].img">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="view-width-100" @click="modelNavigateTo(res.list[1])">
|
||||
<div class="view-height-85">
|
||||
<div class="view-width-100" :style="gapStyle" @click="modelNavigateTo(res.list[1])">
|
||||
<div class="view-height-85" :style="imgStyle">
|
||||
<u-image class="image-mode" width="100%" height="170rpx" :src="res.list[1].img">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="view-height-85" @click="modelNavigateTo(res.list[2])">
|
||||
<div class="view-height-85" :style="imgStyle" @click="modelNavigateTo(res.list[2])">
|
||||
<u-image class="image-mode" width="100%" height="170rpx" :src="res.list[2].img">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
@@ -22,9 +22,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -41,16 +46,17 @@ defineProps<{ res: any }>();
|
||||
// height: 85px !important;
|
||||
// }
|
||||
.view-width-100 {
|
||||
padding: 1px 0;
|
||||
padding: 2rpx 0;
|
||||
display: flex;
|
||||
height: 200rpx;
|
||||
width: 100%;
|
||||
}
|
||||
.view-height-85 {
|
||||
padding: 0 1px;
|
||||
width: 50%;
|
||||
padding: 0 2rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.image-mode{
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="view-width-100">
|
||||
<div class="view-height-85" @click="modelNavigateTo(res.list[0])">
|
||||
<div class="layout" :style="[wrapperStyle, gapStyle]">
|
||||
<div class="view-width-100" :style="gapStyle">
|
||||
<div class="view-height-85" :style="imgStyle" @click="modelNavigateTo(res.list[0])">
|
||||
<u-image
|
||||
class="image-mode"
|
||||
height="170rpx"
|
||||
@@ -12,7 +12,7 @@
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="view-height-85" @click="modelNavigateTo(res.list[1])">
|
||||
<div class="view-height-85" :style="imgStyle" @click="modelNavigateTo(res.list[1])">
|
||||
<u-image
|
||||
class="image-mode"
|
||||
height="170rpx"
|
||||
@@ -24,7 +24,7 @@
|
||||
</u-image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-width-100" @click="modelNavigateTo(res.list[2])">
|
||||
<div class="view-width-100" :style="imgStyle" @click="modelNavigateTo(res.list[2])">
|
||||
<u-image
|
||||
class="image-mode"
|
||||
height="200rpx"
|
||||
@@ -38,9 +38,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { modelNavigateTo } from "./tpl";
|
||||
import { computed } from "vue";
|
||||
import { modelNavigateTo, cubeWrapperStyle, cubeImgStyle, cubeGapStyle } from "./tpl";
|
||||
|
||||
defineProps<{ res: any }>();
|
||||
const props = defineProps<{ res: any }>();
|
||||
|
||||
const wrapperStyle = computed(() => cubeWrapperStyle(props.res));
|
||||
const imgStyle = computed(() => cubeImgStyle(props.res));
|
||||
const gapStyle = computed(() => cubeGapStyle(props.res));
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./tpl.scss";
|
||||
@@ -57,14 +62,15 @@ defineProps<{ res: any }>();
|
||||
// height: 85px !important;
|
||||
// }
|
||||
.view-width-100 {
|
||||
padding: 1px 0;
|
||||
padding: 2rpx 0;
|
||||
display: flex;
|
||||
height: 200rpx;
|
||||
width: 100%;
|
||||
}
|
||||
.view-height-85 {
|
||||
padding: 0 1px;
|
||||
width: 50%;
|
||||
padding: 0 2rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.image-mode{
|
||||
width: 100%;
|
||||
|
||||
@@ -91,7 +91,26 @@ const item = computed(() => {
|
||||
return data;
|
||||
});
|
||||
|
||||
// 面板保存过样式字段(options 级)才启用新样式,老数据走 item.style 体系
|
||||
const styled = computed(() => props.res && props.res.paddingTop !== undefined);
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const res = props.res || {};
|
||||
if (styled.value) {
|
||||
const bg =
|
||||
!res.bgColor2 || res.bgColor1 === res.bgColor2
|
||||
? res.bgColor1
|
||||
: `linear-gradient(180deg, ${res.bgColor1}, ${res.bgColor2})`;
|
||||
const radius = Number(res.borderRadius) || 0;
|
||||
return {
|
||||
padding: `${Number(res.paddingTop) || 0}rpx ${Number(res.paddingX) || 0}rpx ${Number(res.paddingBottom) || 0}rpx`,
|
||||
background: bg,
|
||||
borderRadius: `${radius}rpx`,
|
||||
marginBottom: `${Number(res.pageGap) || 0}rpx`,
|
||||
overflow: radius > 0 ? "hidden" : "visible",
|
||||
boxSizing: "border-box",
|
||||
};
|
||||
}
|
||||
const style = item.value.style;
|
||||
return {
|
||||
margin: `${style.margin || 0}px`,
|
||||
@@ -102,6 +121,14 @@ const wrapperStyle = computed(() => {
|
||||
});
|
||||
|
||||
const boxStyle = computed(() => {
|
||||
if (styled.value) {
|
||||
return {
|
||||
borderRadius: `${Number(props.res.imgRadius) || 0}rpx`,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
};
|
||||
}
|
||||
const style = item.value.style;
|
||||
const box: Record<string, string> = {
|
||||
borderRadius: `${style.bgRadius || 0}px`,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="wrapper" :style="pageBgStyle">
|
||||
<!-- uni 中不能使用 vue component 所以用if判断每个组件 -->
|
||||
<div v-for="(item, index) in pageData.list" :key="index">
|
||||
<!-- 搜索栏,如果在楼层装修顶部则会自动浮动,否则不浮动 -->
|
||||
<div class="navbar" v-if="item.type == 'search'">
|
||||
<div class="navbar" v-if="item.type == 'search'" :style="{ background: topColor }">
|
||||
<search style="width: 100%" :res="item.options" />
|
||||
<!-- #ifndef H5 -->
|
||||
<!-- 扫码功能 不兼容h5 详情文档: https://uniapp.dcloud.io/api/system/barcode?id=scancode -->
|
||||
@@ -12,6 +12,7 @@
|
||||
</div>
|
||||
<!-- #endif -->
|
||||
</div>
|
||||
<homeComb v-if="item.type == 'homeComb'" :res="item.options" />
|
||||
<carousel v-if="item.type == 'carousel'" :res="item.options" />
|
||||
<titleLayout v-if="item.type == 'title'" :res="item.options" />
|
||||
<leftOneRightTwo
|
||||
@@ -41,16 +42,22 @@
|
||||
<group v-if="item.type == 'group'" :res="item.options" />
|
||||
<notice v-if="item.type == 'notice'" :res="item.options" />
|
||||
<promotions v-if="item.type == 'promotionDetail'" :res="item.options" />
|
||||
<!-- <integral v-if="item.type == 'integral'" :res="item.options" /> -->
|
||||
<integral v-if="item.type == 'integral'" :res="item.options" />
|
||||
<!-- <spike v-if="item.type == 'spike'" :res="item.options" /> -->
|
||||
<videoLayout v-if="item.type == 'video'" :res="item.options" />
|
||||
<blank v-if="item.type == 'blank'" :res="item.options" />
|
||||
<lineLayout v-if="item.type == 'line'" :res="item.options" />
|
||||
<richText v-if="item.type == 'richText'" :res="item.options" />
|
||||
<coupon v-if="item.type == 'coupon'" :res="item.options" />
|
||||
<articleList v-if="item.type == 'article'" :res="item.options" />
|
||||
<shopList v-if="item.type == 'shop'" :res="item.options" />
|
||||
</div>
|
||||
<u-no-network></u-no-network>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import carousel from '@/pages/tabbar/home/template/tpl_banner'
|
||||
import titleLayout from '@/pages/tabbar/home/template/tpl_title'
|
||||
@@ -71,6 +78,14 @@ import videoLayout from '@/pages/tabbar/home/template/tpl_video'
|
||||
import goods from '@/pages/tabbar/home/template/tpl_goods'
|
||||
import notice from '@/pages/tabbar/home/template/tpl_notice'
|
||||
import promotions from '@/pages/tabbar/home/template/tpl_promotions_detail'
|
||||
import blank from '@/pages/tabbar/home/template/tpl_blank'
|
||||
import lineLayout from '@/pages/tabbar/home/template/tpl_line'
|
||||
import richText from '@/pages/tabbar/home/template/tpl_rich_text'
|
||||
import integral from '@/pages/tabbar/home/template/tpl_integral'
|
||||
import coupon from '@/pages/tabbar/home/template/tpl_coupon'
|
||||
import articleList from '@/pages/tabbar/home/template/tpl_article'
|
||||
import shopList from '@/pages/tabbar/home/template/tpl_shop'
|
||||
import homeComb from '@/pages/tabbar/home/template/tpl_home_comb.vue'
|
||||
import { toSpecial, getSpecial } from '@/api/home'
|
||||
import permision from '@/js_sdk/wa-permission/permission.js'
|
||||
import config from '@/config/config'
|
||||
@@ -80,6 +95,42 @@ const pageData = ref<any>('')
|
||||
const bodyParam = ref('')
|
||||
const isIos = ref('')
|
||||
|
||||
const topColor = computed(() => pageData.value?.topColor || '#ffffff')
|
||||
const pageBgStyle = computed(() => {
|
||||
const data: any = pageData.value
|
||||
if (data?.bgType === 'image' && data?.bgImage) {
|
||||
const size =
|
||||
data.bgFill === 'contain'
|
||||
? 'contain'
|
||||
: data.bgFill === 'cover'
|
||||
? 'cover'
|
||||
: '100% 100%'
|
||||
return {
|
||||
backgroundImage: `url(${data.bgImage})`,
|
||||
backgroundSize: size,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundColor: data.bgColor || '#f9f9f9',
|
||||
}
|
||||
}
|
||||
if (data?.bgColor) {
|
||||
return { backgroundColor: data.bgColor }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
function applyPageSettings(parsed: any) {
|
||||
if (!parsed) return
|
||||
if (parsed.title) {
|
||||
uni.setNavigationBarTitle({ title: parsed.title })
|
||||
}
|
||||
if (parsed.topColor || parsed.titleColor) {
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: parsed.titleColor === 'white' ? '#ffffff' : '#000000',
|
||||
backgroundColor: parsed.topColor || '#ffffff',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
// #ifdef MP-WEIXIN
|
||||
@@ -98,13 +149,23 @@ function init() {
|
||||
if (bodyParam.value) {
|
||||
toSpecial({ body: bodyParam.value }).then((res) => {
|
||||
if (res.data.success) {
|
||||
pageData.value = JSON.parse(res.data.result.pageData)
|
||||
const parsed = JSON.parse(res.data.result.pageData)
|
||||
if (parsed && Array.isArray(parsed.list)) {
|
||||
parsed.list = parsed.list.filter((i: any) => !i?.hidden)
|
||||
}
|
||||
pageData.value = parsed
|
||||
applyPageSettings(parsed)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
getSpecial(id.value).then((res) => {
|
||||
if (res.data.success) {
|
||||
pageData.value = JSON.parse(res.data.result.pageData)
|
||||
const parsed = JSON.parse(res.data.result.pageData)
|
||||
if (parsed && Array.isArray(parsed.list)) {
|
||||
parsed.list = parsed.list.filter((i: any) => !i?.hidden)
|
||||
}
|
||||
pageData.value = parsed
|
||||
applyPageSettings(parsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -190,4 +251,4 @@ async function scan() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -181,7 +181,7 @@ body {
|
||||
background-size: cover;
|
||||
border-bottom-left-radius: 30rpx;
|
||||
border-bottom-right-radius: 30rpx;
|
||||
background-image: url("/static/img/main-bg.png");
|
||||
background-image: url("https://lilishop-uniapp.oss-cn-beijing.aliyuncs.com/main-bg.png");
|
||||
background-position: bottom;
|
||||
background-repeat: no-repeat;
|
||||
color: #ffffff;
|
||||
|
||||