857 lines
22 KiB
Vue
Raw Normal View History

2025-11-12 14:15:43 +08:00
<!-- 页面 -->
<template>
2025-11-24 13:41:38 +08:00
<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)"></image>
</view>
<image class="xs-img-box" :src="sheep.$url.cdn(data.image)" mode="aspectFit"></image>
<view v-if="goodsFields.title?.show || goodsFields.price?.show"
class="xs-goods-content ss-flex-col ss-row-around">
<view v-if="goodsFields.title?.show" class="xs-goods-title ss-line-1"
:style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]">
{{ data.title }}
</view>
<view v-if="goodsFields.price?.show && isLogin == true" class="xs-goods-price font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]">
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
{{ isArray(data.price) ? data.price[0] : data.price }}
</view>
<view class="goods-price" v-if="isLogin == false" @click.stop="toLogin">登录查看价格</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)"></image>
</view>
<image class="sm-img-box" :src="sheep.$url.cdn(data.image)" mode="aspectFill"></image>
<view v-if="goodsFields.title?.show || goodsFields.price?.show" class="sm-goods-content"
:style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]">
<view v-if="goodsFields.title?.show" class="sm-goods-title ss-line-1 ss-m-b-16">
{{ data.title }}
</view>
<view v-if="goodsFields.price?.show && isLogin == true" class="sm-goods-price font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]">
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
{{ isArray(data.price) ? data.price[0] : data.price }}
</view>
<view class="goods-price" v-if="isLogin == false" @click.stop="toLogin">登录查看价格</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)"></image>
</view>
<image v-if="data.image_wh" class="md-img-box" :src="sheep.$url.cdn(data.image)" mode="widthFix" />
<image v-else class="md-img-box" :src="sheep.$url.cdn(data.image)"
:style="[{ height: defaultImgWidth * 2 + 'rpx' }]" mode="aspectFill" />
<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" class="md-goods-title ss-line-1"
:style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]">
{{ data.title }}
</view>
<view v-if="goodsFields.subtitle?.show" class="md-goods-subtitle ss-m-t-16 ss-line-1"
:style="[{ color: subTitleColor, background: subTitleBackground }]">
{{ data.subtitle }}
</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="ss-flex ss-col-bottom">
<view v-if="goodsFields.price?.show && isLogin == true"
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>
{{ isArray(data.price) ? data.price[0] : data.price }}
</view>
<view class="goods-price" v-if="isLogin == false" @click.stop="toLogin">登录查看价格</view>
<view v-if="goodsFields.original_price?.show && data.original_price > 0 && isLogin == true"
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">{{ data.original_price }}</view>
</view>
</view>
<view class="ss-m-t-16 ss-flex ss-col-center ss-flex-wrap">
<view class="sales-text" style="text-align: left;">{{ salesAndStock }}</view>
</view>
</view>
<slot name="cart"> </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)"></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)" 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" class="lg-goods-title ss-line-2"
:style="[{ color: titleColor }]">
{{ data.title }}
</view>
<view v-if="goodsFields.subtitle?.show" class="lg-goods-subtitle ss-m-t-10 ss-line-1"
:style="[{ color: subTitleColor, background: subTitleBackground }]">
{{ data.subtitle }}
</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="ss-flex ss-col-bottom ss-m-t-10">
<view v-if="goodsFields.price?.show && isLogin == true"
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) ? data.price[0] : data.price }}
</view>
<view class="goods-price" v-if="isLogin == false" @click.stop="toLogin">登录查看价格</view>
<view v-if="goodsFields.original_price?.show && data.original_price > 0 && isLogin == true"
class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS"
:style="[{ color: originPriceColor }]">
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
<view class="ss-m-l-8">{{ data.original_price }}</view>
</view>
</view>
<view class="ss-m-t-8 ss-flex ss-col-center ss-flex-wrap">
<view class="sales-text" style="text-align: left;">{{ 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)"></image>
</view>
<image class="sl-img-box" :src="sheep.$url.cdn(data.image)" mode="aspectFill"></image>
<view class="sl-goods-content">
<view>
<view v-if="goodsFields.title?.show" class="sl-goods-title ss-line-1"
:style="[{ color: titleColor }]">
{{ data.title }}
</view>
<view v-if="goodsFields.subtitle?.show" class="sl-goods-subtitle ss-m-t-16"
:style="[{ color: subTitleColor, background: subTitleBackground }]">
{{ data.subtitle }}
</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 v-if="goodsFields.price?.show && isLogin == true" 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>
{{ isArray(data.price) ? data.price[0] : data.price }}
</view>
<view class="goods-price" v-if="isLogin == false" @click.stop="toLogin">登录查看价格</view>
<view v-if="goodsFields.original_price?.show && data.original_price > 0 && isLogin == true"
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">{{ data.original_price }}</view>
</view>
</view>
<view class="ss-m-t-16 ss-flex ss-flex-wrap">
<view class="sales-text" style="text-align: left;">{{ salesAndStock }}</view>
</view>
</view>
</view>
<slot name="cart">
<view class="buy-box ss-flex ss-col-center ss-row-center">去购买</view>
</slot>
</view>
2025-11-12 14:15:43 +08:00
</view>
2025-11-24 13:41:38 +08:00
<su-popup :show="showLogin == true" type="center" round="10" @close="showLogin = false" @open="showLogin = true">
<view class="popup">
<button class="texbut" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
<!-- <image style="width: 100%;height: 136rpx;" src="https://mall.jiangxiaoxian.hschool.com.cn/loginBtnTwo.png"></image> -->
<!-- 授权登录 -->
</button>
</view>
<view style="margin: 0 auto;position: absolute;bottom: -90rpx;width: 100%;text-align: center;">
<image style="width: 50rpx;height: 50rpx;"
src="https://mall.jiangxiaoxian.hschool.com.cn/registerCancel.png" mode="aspectFill"
@click="showLogin = false"></image>
</view>
</su-popup>
2025-11-12 14:15:43 +08:00
</template>
<script setup>
2025-11-24 13:41:38 +08:00
/**
* 商品卡片
*
* @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,
ref,
reactive,
getCurrentInstance,
onMounted,
nextTick
} from 'vue';
import sheep from '@/sheep';
import {
formatSales
} from '@/sheep/hooks/useGoods';
import {
formatStock
} from '@/sheep/hooks/useGoods';
import {
isArray
} from 'lodash';
import third from '@/sheep/api/third';
// 数据
let defaultImgWidth = ref(0);
// 接收参数
const props = defineProps({
goodsFields: {
type: [Array, Object],
default () {
return {
title: {
show: true,
},
subtitle: {
show: true,
},
price: {
show: true,
},
original_price: {
show: true,
},
sales: {
show: true,
},
stock: {
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: '#999999',
},
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 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.sales?.show) {
text.push(formatSales(props.data.sales_show_type, props.data.sales));
}
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];
defaultImgWidth.value = data[0].width;
if (props.data.image_wh && Number(props.data.image_wh.w)) {
totalHeight =
(goodsPriceCard.width / props.data.image_wh.w) * props.data.image_wh.h +
goodsPriceCard.height;
} else {
totalHeight = goodsPriceCard.width + goodsPriceCard.height;
}
emits('getHeight', totalHeight);
});
}
}
onMounted(() => {
nextTick(() => {
getGoodsPriceCardWH();
});
});
const isLogin = computed(() => sheep.$store('user').isLogin);
const showLogin = ref(false);
const iv = ref('');
const wxCode = ref('');
const encryptedData = ref('');
// 打开登录的弹框
function toLogin() {
console.log('toLogin——打开登录的弹框', showLogin.value);
// showLogin.value = true;
uni.reLaunch({
url: '/pages/index/login'
});
console.log('showLogin', showLogin.value);
}
// 关闭登录弹框
function closeLogin() {
console.log('closeLogin');
showLogin.value = false;
}
// 打开登录的弹框
function openLogin() {
console.log('openLogin');
showLogin.value = true;
}
// 使用微信手机号
async function getPhoneNumber(e) {
console.log('获取手机号', e, e.detail.errMsg);
if (e.detail.errMsg !== 'getPhoneNumber:ok') {} else {
encryptedData.value = e.detail.encryptedData // 用于解密
iv.value = e.detail.iv // 用于解密
getCode()
// let result = await sheep.$platform.useProvider().bindUserPhoneNumber(e.detail);
// console.log('result', result);
// if (result) {
// sheep.$store('user').getInfo();
// // closeAuthModal();
// closeLogin();
// }
}
}
// 获取code和sessionId
function getCode() {
uni.login({
provider: 'weixin',
success: (res) => {
wxCode.value = res.code
// loginResult.value = res
// getSessionId();
Login();
}
})
}
// 登录接口
function Login() {
const session_id = uni.getStorageSync('sessionId')
console.log('session_id', session_id);
return new Promise(async (resolve, reject) => {
const {
code
} = await third.wechat.wxLogin({
platform: 'miniProgram',
payload: encodeURIComponent(
JSON.stringify({
sessionId: session_id,
iv: iv.value,
encryptedData: encryptedData.value,
code: wxCode.code,
}),
),
});
if (code === 1) {
resolve(true);
showLogin.value = false;
}
resolve(false);
});
}
2025-11-12 14:15:43 +08:00
</script>
<style lang="scss" scoped>
.popup {
background-image: url('https://mall.jiangxiaoxian.hschool.com.cn/loginBack.png');
background-size: 100%;
width: 540rpx;
height: 640rpx;
display: flex;
padding-bottom: 44rpx;
flex-direction: column;
align-items: baseline;
justify-content: flex-end;
padding-left: 73rpx;
padding-right: 73rpx;
2025-11-24 13:41:38 +08:00
2025-11-12 14:15:43 +08:00
.img_shouquan {
position: absolute;
width: 680rpx;
height: 857rpx;
left: -25rpx;
top: -390rpx;
}
2025-11-24 13:41:38 +08:00
2025-11-12 14:15:43 +08:00
#txt2 {
text-align: center;
margin-top: 200rpx;
font-size: 28rpx;
color: #525252;
margin-bottom: 60rpx;
position: relative;
font-weight: 500;
}
2025-11-24 13:41:38 +08:00
2025-11-12 14:15:43 +08:00
.texbut {
width: 507rpx;
height: 93rpx;
border-radius: 16rpx;
// background: #ffbd25;
background-image: url('https://mall.jiangxiaoxian.hschool.com.cn/loginBtnTwo.png');
background-size: 100%;
font-size: 34rpx;
color: #3d3d3d;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
margin-top: 30rpx;
// margin: 0 auto;
}
}
2025-11-24 13:41:38 +08:00
2025-11-12 14:15:43 +08:00
.goods-price {
2025-11-24 13:41:38 +08:00
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #e1212b;
margin-top: 16rpx;
}
.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: 2px;
}
.sales-text {
display: table;
text-align: center;
font-size: 22rpx;
// transform: scale(0.8);
margin-left: 0rpx;
color: #999999;
}
.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: #999999;
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: 30rpx;
color: #333;
font-weight: bold;
}
.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: 30rpx;
font-weight: bold;
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: 30rpx;
font-weight: bold;
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: 30rpx;
font-weight: bold;
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;
margin-right: 4rpx;
}
.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: 30rpx;
color: #333;
font-weight: bold;
}
.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;
}
2025-11-12 14:15:43 +08:00
}
2025-11-24 13:41:38 +08:00
</style>