mall-uniapp/sheep/components/s-goods-column/s-goods-column.vue

792 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 页面 -->
<template>
<view class="ss-goods-wrap">
<!-- xs卡片横向紧凑型一行放两个图片左内容右边 -->
<view v-if="size === 'xs'" class="xs-goods-card ss-flex ss-col-stretch" :style="[elStyles]" @tap="onClick">
<view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
</view>
<image class="xs-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFit"></image>
<view v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show"
class="xs-goods-content ss-flex-col ss-row-around">
<view v-if="goodsFields.title?.show || goodsFields.name?.show" class="xs-goods-title ss-line-1"
:style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]">
{{ data.title || data.name }}
</view>
<!-- 这里是新加的会员价和限时优惠 -->
<view class="iconBox" v-if="data.discountPrice || data.vipPrice || data.reward">
<view class="card" v-if="iconShow">{{iconShow}}</view>
<view class="card2" v-if="data.reward">{{data.reward.rewardActivity}}</view>
</view>
<!-- 这里是新加的会员价和限时优惠结束 -->
<view v-if="goodsFields.price?.show" class="xs-goods-price font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]">
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="iconShow=='限时优惠'">{{fen2yuan(data.discountPrice)}}</text>
<text v-else-if="iconShow=='会员价'">{{fen2yuan(data.vipPrice)}}</text>
<text v-else>{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}</text>
</view>
</view>
</view>
<!-- sm卡片竖向紧凑一行放三个图上内容下 -->
<view v-if="size === 'sm'" class="sm-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
<view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
</view>
<image class="sm-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
<view v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show"
class="sm-goods-content" :style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]">
<view v-if="goodsFields.title?.show || goodsFields.name?.show"
class="sm-goods-title ss-line-1 ss-m-b-16">
{{ data.title || data.name }}
</view>
<!-- 这里是新加的会员价和限时优惠 -->
<view class="iconBox" v-if="data.discountPrice || data.vipPrice || data.reward">
<view class="card" v-if="iconShow">{{iconShow}}</view>
<view class="card2" v-if="data.reward">{{data.reward.rewardActivity}}</view>
</view>
<!-- 这里是新加的会员价和限时优惠结束 -->
<view v-if="goodsFields.price?.show" class="sm-goods-price font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]">
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="iconShow=='限时优惠'">{{fen2yuan(data.discountPrice)}}</text>
<text v-else-if="iconShow=='会员价'">{{fen2yuan(data.vipPrice)}}</text>
<text v-else>{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}</text>
</view>
</view>
</view>
<!-- md卡片竖向一行放两个图上内容下 -->
<view v-if="size === 'md'" class="md-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
<view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
</view>
<image class="md-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="widthFix"></image>
<view class="md-goods-content ss-flex-col ss-row-around ss-p-b-20 ss-p-t-20 ss-p-x-16" :id="elId">
<view v-if="goodsFields.title?.show || goodsFields.name?.show" class="md-goods-title ss-line-1"
:style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]">
{{ data.title || data.name }}
</view>
<view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
class="md-goods-subtitle ss-m-t-16 ss-line-1"
:style="[{ color: subTitleColor, background: subTitleBackground }]">
{{ data.subtitle || data.introduction }}
</view>
<slot name="activity">
<view v-if="data.promos?.length" class="tag-box ss-flex-wrap ss-flex ss-col-center">
<view class="activity-tag ss-m-r-10 ss-m-t-16" v-for="item in data.promos" :key="item.id">
{{ item.title }}
</view>
</view>
</slot>
<!-- 这里是新加的会员价和限时优惠 -->
<view class="iconBox" v-if="data.discountPrice || data.vipPrice || data.reward">
<view class="card" v-if="iconShow">{{iconShow}}</view>
<view class="card2" v-if="data.reward">{{data.reward.rewardActivity}}</view>
</view>
<!-- 这里是新加的会员价和限时优惠结束 -->
<view class="ss-flex ss-col-bottom">
<view v-if="goodsFields.price?.show" class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10"
:style="[{ color: goodsFields.price.color }]">
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="iconShow=='限时优惠'">{{fen2yuan(data.discountPrice)}}</text>
<text v-else-if="iconShow=='会员价'">{{fen2yuan(data.vipPrice)}}</text>
<text v-else>{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}</text>
</view>
<view v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
(data.original_price > 0 || data.marketPrice > 0)
" class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex" :style="[{ color: originPriceColor }]">
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
<view class="ss-m-l-8">{{ fen2yuan(data.marketPrice) }}</view>
</view>
</view>
<view class="ss-m-t-16 ss-flex ss-col-center ss-flex-wrap">
<view class="sales-text">{{ salesAndStock }}</view>
</view>
</view>
<slot name="cart">
<view class="cart-box ss-flex ss-col-center ss-row-center">
<image class="cart-icon" src="/static/img/shop/tabbar/category2.png" mode="" />
</view>
</slot>
</view>
<!-- lg卡片横向型一行放一个图片左内容右边 -->
<view v-if="size === 'lg'" class="lg-goods-card ss-flex ss-col-stretch" :style="[elStyles]" @tap="onClick">
<view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
</view>
<view v-if="seckillTag" class="seckill-tag ss-flex ss-row-center"> 秒杀 </view>
<view v-if="grouponTag" class="groupon-tag ss-flex ss-row-center">
<view class="tag-icon">拼团</view>
</view>
<image class="lg-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
<view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
<view>
<view v-if="goodsFields.title?.show || goodsFields.name?.show" class="lg-goods-title ss-line-2"
:style="[{ color: titleColor }]">
{{ data.title || data.name }}
</view>
<view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
class="lg-goods-subtitle ss-m-t-10 ss-line-1"
:style="[{ color: subTitleColor, background: subTitleBackground }]">
{{ data.subtitle || data.introduction }}
</view>
</view>
<view>
<slot name="activity">
<view v-if="data.promos?.length" class="tag-box ss-flex ss-col-center">
<view class="activity-tag ss-m-r-10" v-for="item in data.promos" :key="item.id">
{{ item.title }}
</view>
</view>
</slot>
<!-- 这里是新加的会员价和限时优惠 -->
<view class="iconBox" v-if="data.discountPrice || data.vipPrice || data.reward">
<view class="card" v-if="iconShow">{{iconShow}}</view>
<view class="card2" v-if="data.reward">{{data.reward.rewardActivity}}</view>
</view>
<!-- 这里是新加的会员价和限时优惠结束 -->
<view class="ss-flex ss-col-bottom ss-m-t-10">
<view v-if="goodsFields.price?.show"
class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]">
<text class="ss-font-24">{{ priceUnit }}</text>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</view>
<view v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
(data.original_price > 0 || data.marketPrice > 0)
" class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS" :style="[{ color: originPriceColor }]">
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
<text v-if="iconShow=='限时优惠'">{{fen2yuan(data.discountPrice)}}</text>
<text v-else-if="iconShow=='会员价'">{{fen2yuan(data.vipPrice)}}</text>
<text v-else>{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}</text>
</view>
</view>
<view class="ss-m-t-8 ss-flex ss-col-center ss-flex-wrap">
<view class="sales-text">{{ salesAndStock }}</view>
</view>
</view>
</view>
<slot name="cart">
<view class="buy-box ss-flex ss-col-center ss-row-center" v-if="buttonShow"> 去购买 </view>
</slot>
</view>
<!-- sl卡片竖向型一行放一个图片上内容下边 -->
<view v-if="size === 'sl'" class="sl-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
<view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
</view>
<image class="sl-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
<view class="sl-goods-content">
<view>
<view v-if="goodsFields.title?.show || goodsFields.name?.show" class="sl-goods-title ss-line-1"
:style="[{ color: titleColor }]">
{{ data.title || data.name }}
</view>
<view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
class="sl-goods-subtitle ss-m-t-16"
:style="[{ color: subTitleColor, background: subTitleBackground }]">
{{ data.subtitle || data.introduction }}
</view>
</view>
<view>
<slot name="activity">
<view v-if="data.promos?.length" class="tag-box ss-flex ss-col-center ss-flex-wrap">
<view class="activity-tag ss-m-r-10 ss-m-t-16" v-for="item in data.promos" :key="item.id">
{{ item.title }}
</view>
</view>
</slot>
<!-- 这里是新加的会员价和限时优惠 -->
<view class="iconBox" v-if="data.discountPrice || data.vipPrice || data.reward">
<view class="card" v-if="iconShow">{{iconShow}}</view>
<view class="card2" v-if="data.reward">{{data.reward.rewardActivity}}</view>
</view>
<!-- 这里是新加的会员价和限时优惠结束 -->
<view v-if="goodsFields.price?.show" class="ss-flex ss-col-bottom font-OPPOSANS">
<view class="sl-goods-price ss-m-r-12" :style="[{ color: goodsFields.price.color }]">
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="iconShow=='限时优惠'">{{fen2yuan(data.discountPrice)}}</text>
<text v-else-if="iconShow=='会员价'">{{fen2yuan(data.vipPrice)}}</text>
<text v-else>{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}</text>
</view>
<view v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
(data.original_price > 0 || data.marketPrice > 0)
" class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex" :style="[{ color: originPriceColor }]">
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
<view class="ss-m-l-8">{{ fen2yuan(data.marketPrice) }}</view>
</view>
</view>
<view class="ss-m-t-16 ss-flex ss-flex-wrap">
<view class="sales-text">{{ salesAndStock }}</view>
</view>
</view>
</view>
<slot name="cart">
<view class="buy-box ss-flex ss-col-center ss-row-center"></view>
</slot>
</view>
</view>
</template>
<script setup>
/**
* 商品卡片
*
* @property {Array} size = [xs | sm | md | lg | sl ] - 列表数据
* @property {String} tag - md及以上才有
* @property {String} img - 图片
* @property {String} background - 背景色
* @property {String} topRadius - 上圆角
* @property {String} bottomRadius - 下圆角
* @property {String} title - 标题
* @property {String} titleColor - 标题颜色
* @property {Number} titleWidth = 0 - 标题宽度默认0单位rpx
* @property {String} subTitle - 副标题
* @property {String} subTitleColor - 副标题颜色
* @property {String} subTitleBackground - 副标题背景
* @property {String | Number} price - 价格
* @property {String} priceColor - 价格颜色
* @property {String | Number} originPrice - 原价/划线价
* @property {String} originPriceColor - 原价颜色
* @property {String | Number} sales - 销售数量
* @property {String} salesColor - 销售数量颜色
*
* @slots activity - 活动插槽
* @slots cart - 购物车插槽,默认包含文字,背景色,文字颜色 || 图片 || 行为
*
* @event {Function()} click - 点击卡片
*
*/
import {
computed,
reactive,
getCurrentInstance,
onMounted,
nextTick,
ref
} from 'vue';
import sheep from '@/sheep';
import {
fen2yuan,
formatSales
} from '@/sheep/hooks/useGoods';
import {
formatStock
} from '@/sheep/hooks/useGoods';
import goodsCollectVue from '@/pages/user/goods-collect.vue';
import {
isArray
} from 'lodash-es';
// 数据
const state = reactive({});
// 接收参数
const props = defineProps({
goodsFields: {
type: [Array, Object],
default () {
return {
// 商品价格
price: {
show: true
},
// 库存
stock: {
show: true
},
// 商品名称
name: {
show: true
},
// 商品介绍
introduction: {
show: true
},
// 市场价
marketPrice: {
show: true
},
// 销量
salesCount: {
show: true
},
};
},
},
tagStyle: {
type: Object,
default: {},
},
data: {
type: Object,
default: {},
},
size: {
type: String,
default: 'sl',
},
background: {
type: String,
default: '',
},
topRadius: {
type: Number,
default: 0,
},
bottomRadius: {
type: Number,
default: 0,
},
titleWidth: {
type: Number,
default: 0,
},
titleColor: {
type: String,
default: '#333',
},
priceColor: {
type: String,
default: '',
},
originPriceColor: {
type: String,
default: '#C4C4C4',
},
priceUnit: {
type: String,
default: '¥',
},
subTitleColor: {
type: String,
default: '#999999',
},
subTitleBackground: {
type: String,
default: '',
},
buttonShow: {
type: Boolean,
default: true,
},
seckillTag: {
type: Boolean,
default: false,
},
grouponTag: {
type: Boolean,
default: false,
},
});
//判断限时优惠和会员价标签内容暂时导致页面出错,又舍不得丢,等着把新的数据整合到商品信息中,也用起来
const iconShow = handle()
function handle() {
if (props.data.discountPrice === null && props.data.vipPrice === null) {
// 如果两个值都为 null则不展示任何内容
return '';
} else if (props.data.discountPrice === null) {
// 如果 discountPrice 为 null展示 vipPrice
return '会员价';
} else if (props.data.vipPrice === null) {
// 如果 vipPrice 为 null展示 discountPrice
return '限时优惠';
} else if (props.data.discountPrice < props.data.vipPrice) {
return '限时优惠';
} else if (props.data.discountPrice > props.data.vipPrice) {
return '会员价';
}
}
// 组件样式
const elStyles = computed(() => {
return {
background: props.background,
'border-top-left-radius': props.topRadius + 'px',
'border-top-right-radius': props.topRadius + 'px',
'border-bottom-left-radius': props.bottomRadius + 'px',
'border-bottom-right-radius': props.bottomRadius + 'px',
};
});
// 格式化销量、库存信息
const salesAndStock = computed(() => {
let text = [];
if (props.goodsFields.salesCount?.show) {
text.push(formatSales(props.data.sales_show_type, props.data.salesCount));
}
if (props.goodsFields.stock?.show) {
text.push(formatStock(props.data.stock_show_type, props.data.stock));
}
return text.join(' | ');
});
// 返回事件
const emits = defineEmits(['click', 'getHeight']);
const onClick = () => {
emits('click');
};
// 获取卡片实时高度
const {
proxy
} = getCurrentInstance();
const elId = `sheep_${Math.ceil(Math.random() * 10e5).toString(36)}`;
function getGoodsPriceCardWH() {
if (props.size === 'md') {
const view = uni.createSelectorQuery().in(proxy);
view.select(`#${elId}`).fields({
size: true,
scrollOffset: true
});
view.exec((data) => {
let totalHeight = 0;
const goodsPriceCard = data[0];
if (props.data.image_wh) {
totalHeight =
(goodsPriceCard.width / props.data.image_wh.w) * props.data.image_wh.h +
goodsPriceCard.height;
} else {
totalHeight = goodsPriceCard.width;
}
emits('getHeight', totalHeight);
});
}
}
onMounted(() => {
nextTick(() => {
getGoodsPriceCardWH();
});
});
</script>
<style lang="scss" scoped>
.tag-icon-box {
position: absolute;
left: 0;
top: 0;
z-index: 2;
.tag-icon {
width: 72rpx;
height: 44rpx;
}
}
.seckill-tag {
position: absolute;
left: 0;
top: 0;
z-index: 2;
width: 68rpx;
height: 38rpx;
background: linear-gradient(90deg, #ff5854 0%, #ff2621 100%);
border-radius: 10rpx 0px 10rpx 0px;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
line-height: 32rpx;
}
.groupon-tag {
position: absolute;
left: 0;
top: 0;
z-index: 2;
width: 68rpx;
height: 38rpx;
background: linear-gradient(90deg, #fe832a 0%, #ff6600 100%);
border-radius: 10rpx 0px 10rpx 0px;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
line-height: 32rpx;
}
.goods-img {
width: 100%;
height: 100%;
background-color: #f5f5f5;
}
.price-unit {
margin-right: -4px;
}
.sales-text {
display: table;
font-size: 24rpx;
transform: scale(0.8);
margin-left: 0rpx;
color: #c4c4c4;
}
.activity-tag {
font-size: 20rpx;
color: #ff0000;
line-height: 30rpx;
padding: 0 10rpx;
border: 1px solid rgba(#ff0000, 0.25);
border-radius: 4px;
flex-shrink: 0;
}
.goods-origin-price {
font-size: 20rpx;
color: #c4c4c4;
line-height: 36rpx;
text-decoration: line-through;
}
// xs
.xs-goods-card {
overflow: hidden;
// max-width: 375rpx;
background-color: $white;
position: relative;
.xs-img-box {
width: 128rpx;
height: 128rpx;
margin-right: 20rpx;
}
.xs-goods-title {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.xs-goods-price {
font-size: 30rpx;
color: $red;
}
}
// sm
.sm-goods-card {
overflow: hidden;
// width: 223rpx;
// width: 100%;
background-color: $white;
position: relative;
.sm-img-box {
// width: 228rpx;
width: 100%;
height: 208rpx;
}
.sm-goods-content {
padding: 20rpx 16rpx;
box-sizing: border-box;
}
.sm-goods-title {
font-size: 26rpx;
color: #333;
}
.sm-goods-price {
font-size: 30rpx;
color: $red;
}
}
// md
.md-goods-card {
overflow: hidden;
width: 100%;
position: relative;
z-index: 1;
background-color: $white;
position: relative;
.md-img-box {
width: 100%;
}
.md-goods-title {
font-size: 26rpx;
color: #333;
width: 100%;
}
.md-goods-subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
.md-goods-price {
font-size: 30rpx;
color: $red;
line-height: 36rpx;
}
.cart-box {
width: 54rpx;
height: 54rpx;
background: linear-gradient(90deg, #fe8900, #ff5e00);
border-radius: 50%;
position: absolute;
bottom: 50rpx;
right: 20rpx;
z-index: 2;
.cart-icon {
width: 30rpx;
height: 30rpx;
}
}
}
// lg
.lg-goods-card {
overflow: hidden;
position: relative;
z-index: 1;
background-color: $white;
height: 280rpx;
.lg-img-box {
width: 280rpx;
height: 280rpx;
margin-right: 20rpx;
}
.lg-goods-title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
// line-height: 36rpx;
// width: 410rpx;
}
.lg-goods-subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999999;
// line-height: 30rpx;
// width: 410rpx;
}
.lg-goods-price {
font-size: 30rpx;
color: $red;
line-height: 36rpx;
}
.buy-box {
position: absolute;
bottom: 20rpx;
right: 20rpx;
z-index: 2;
width: 120rpx;
height: 50rpx;
background: linear-gradient(90deg, #fe8900, #ff5e00);
border-radius: 25rpx;
font-size: 24rpx;
color: #ffffff;
}
.tag-box {
width: 100%;
}
}
// sl
.sl-goods-card {
overflow: hidden;
position: relative;
z-index: 1;
width: 100%;
background-color: $white;
.sl-goods-content {
padding: 20rpx 20rpx;
box-sizing: border-box;
}
.sl-img-box {
width: 100%;
height: 360rpx;
}
.sl-goods-title {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.sl-goods-subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999999;
line-height: 30rpx;
}
.sl-goods-price {
font-size: 30rpx;
color: $red;
line-height: 36rpx;
}
.buy-box {
position: absolute;
bottom: 20rpx;
right: 20rpx;
z-index: 2;
width: 148rpx;
height: 50rpx;
background: linear-gradient(90deg, #fe8900, #ff5e00);
border-radius: 25rpx;
font-size: 24rpx;
color: #ffffff;
}
}
.card {
width: fit-content;
height: fit-content;
padding: 2rpx 10rpx;
background-color: red;
color: #ffffff;
font-size: 24rpx;
}
.card2 {
width: fit-content;
height: fit-content;
padding: 2rpx 10rpx;
background-color: rgb(255, 242, 241);
color: #ff2621;
font-size: 24rpx;
margin-left: 5rpx;
}
.iconBox {
width: 100%;
height: fit-content;
margin-top: 10rpx;
display: flex;
justify-content: flex-start;
}
</style>