pull/106/MERGE
YunaiV 2024-09-30 09:05:31 +08:00
commit 25e4686e30
35 changed files with 3909 additions and 1384 deletions

1358
pages.json

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
<view class="type-text ss-flex ss-row-center">满减</view> <view class="type-text ss-flex ss-row-center">满减</view>
<view class="ss-flex-1"> <view class="ss-flex-1">
<view class="tip-content" v-for="item in state.activityInfo.rules" :key="item"> <view class="tip-content" v-for="item in state.activityInfo.rules" :key="item">
{{ formatRewardActivityRule(state.activityInfo, item) }} {{ item.description }}
</view> </view>
</view> </view>
<image class="activity-left-image" src="/static/activity-left.png" /> <image class="activity-left-image" src="/static/activity-left.png" />
@ -65,8 +65,9 @@
import sheep from '@/sheep'; import sheep from '@/sheep';
import _ from 'lodash-es'; import _ from 'lodash-es';
import RewardActivityApi from '@/sheep/api/promotion/rewardActivity'; import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
import { formatRewardActivityRule } from '@/sheep/hooks/useGoods';
import SpuApi from '@/sheep/api/product/spu'; import SpuApi from '@/sheep/api/product/spu';
import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
import OrderApi from '@/sheep/api/trade/order';
const state = reactive({ const state = reactive({
activityId: 0, // activityId: 0, //
@ -123,6 +124,13 @@
if (code !== 0) { if (code !== 0) {
return; return;
} }
//
await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
if (res.code !== 0) {
return;
}
appendSettlementProduct(data.list, res.data);
});
state.pagination.list = _.concat(state.pagination.list, data.list); state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total; state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';

View File

@ -0,0 +1,78 @@
<!-- 积分商城商品列表 -->
<template>
<s-layout title="积分商城">
<view class="ss-p-20">
<view v-for="item in state.pagination.data" :key="item.id" class="ss-m-b-20">
<s-point-card
size="sl"
:data="item"
priceColor="#FF3000"
@tap="sheep.$router.go('/pages/goods/point', { id: item.id })"
/>
</view>
</view>
<s-empty
v-if="state.pagination.total === 0"
icon="/static/goods-empty.png"
text="暂无积分商品"
/>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadmore"
/>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
const state = reactive({
pagination: {
data: [],
current_page: 1,
total: 1,
last_page: 1,
},
loadStatus: '',
});
async function getData(page = 1, list_rows = 5) {
// TODO @puhui999
state.loadStatus = 'loading';
let res = await sheep.$api.app.scoreShop.list({
list_rows,
page,
});
if (res.error === 0) {
let couponlist = _.concat(state.pagination.data, res.data.data);
state.pagination = {
...res.data,
data: couponlist,
};
if (state.pagination.current_page < state.pagination.last_page) {
state.loadStatus = 'more';
} else {
state.loadStatus = 'noMore';
}
}
}
//
function loadmore() {
if (state.loadStatus !== 'noMore') {
getData(state.pagination.current_page + 1);
}
}
//
onReachBottom(() => {
loadmore();
});
onLoad(() => {
getData();
});
</script>

View File

@ -3,13 +3,20 @@
<s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }"> <s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }">
<!--顶部背景图--> <!--顶部背景图-->
<view <view
class="page-bg" class="page-bg"
:style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]" :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]"
></view> ></view>
<!-- 时间段轮播图 --> <!-- 时间段轮播图 -->
<view class="header" v-if="activeTimeConfig?.sliderPicUrls?.length > 0"> <view class="header" v-if="activeTimeConfig?.sliderPicUrls?.length > 0">
<swiper indicator-dots="true" autoplay="true" :circular="true" interval="3000" duration="1500" <swiper
indicator-color="rgba(255,255,255,0.6)" indicator-active-color="#fff"> indicator-dots="true"
autoplay="true"
:circular="true"
interval="3000"
duration="1500"
indicator-color="rgba(255,255,255,0.6)"
indicator-active-color="#fff"
>
<block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index"> <block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index">
<swiper-item class="borRadius14"> <swiper-item class="borRadius14">
<image :src="picUrl" class="slide-image borRadius14" lazy-load /> <image :src="picUrl" class="slide-image borRadius14" lazy-load />
@ -22,17 +29,28 @@
<!-- 左侧图标 --> <!-- 左侧图标 -->
<view class="time-icon"> <view class="time-icon">
<!-- TODO 芋艿图片统一维护 --> <!-- TODO 芋艿图片统一维护 -->
<image class="ss-w-100 ss-h-100" src="http://mall.yudao.iocoder.cn/static/images/priceTag.png" /> <image
class="ss-w-100 ss-h-100"
src="http://mall.yudao.iocoder.cn/static/images/priceTag.png"
/>
</view> </view>
<scroll-view class="time-list" :scroll-into-view="activeTimeElId" scroll-x scroll-with-animation> <scroll-view
<view v-for="(config, index) in timeConfigList" :key="index" class="time-list"
:class="['item', { active: activeTimeIndex === index}]" :scroll-into-view="activeTimeElId"
:id="`timeItem${index}`" scroll-x
@tap="handleChangeTimeConfig(index)"> scroll-with-animation
>
<view
v-for="(config, index) in timeConfigList"
:key="index"
:class="['item', { active: activeTimeIndex === index }]"
:id="`timeItem${index}`"
@tap="handleChangeTimeConfig(index, config.id)"
>
<!-- 活动起始时间 --> <!-- 活动起始时间 -->
<view class="time">{{ config.startTime }}</view> <view class="time">{{ config.startTime }}</view>
<!-- 活动状态 --> <!-- 活动状态 -->
<view class="status">{{ config.status }}</view> <view class="status">{{ config?.status }}</view>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@ -42,7 +60,10 @@
<!-- 活动倒计时 --> <!-- 活动倒计时 -->
<view class="content-header ss-flex-col ss-col-center ss-row-center"> <view class="content-header ss-flex-col ss-col-center ss-row-center">
<view class="content-header-box ss-flex ss-row-center"> <view class="content-header-box ss-flex ss-row-center">
<view class="countdown-box ss-flex" v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"> <view
class="countdown-box ss-flex"
v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"
>
<view class="countdown-title ss-m-r-12">距结束</view> <view class="countdown-title ss-m-r-12">距结束</view>
<view class="ss-flex countdown-time"> <view class="ss-flex countdown-time">
<view class="ss-flex countdown-h">{{ countDown.h }}</view> <view class="ss-flex countdown-h">{{ countDown.h }}</view>
@ -70,19 +91,44 @@
:data="{ ...activity, price: activity.seckillPrice }" :data="{ ...activity, price: activity.seckillPrice }"
:goodsFields="goodsFields" :goodsFields="goodsFields"
:seckillTag="true" :seckillTag="true"
@click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
> >
<!-- 抢购进度 --> <!-- 抢购进度 -->
<template #activity> <template #activity>
<view class="limit">限量 <text class="ss-m-l-5">{{ activity.stock}} {{activity.unitName}}</text></view> <view class="limit">
限量
<text class="ss-m-l-5">{{ activity.stock }} {{ activity.unitName }}</text>
</view>
<su-progress :percentage="activity.percent" strokeWidth="10" textInside isAnimate /> <su-progress :percentage="activity.percent" strokeWidth="10" textInside isAnimate />
</template> </template>
<!-- 抢购按钮 --> <!-- 抢购按钮 -->
<template #cart> <template #cart>
<button :class="['ss-reset-button cart-btn', { disabled: activeTimeConfig.status === TimeStatusEnum.END }]"> <button
<span v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START"></span> :class="[
<span v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">马上抢</span> 'ss-reset-button cart-btn',
<span v-else></span> { disabled: activeTimeConfig?.status === TimeStatusEnum.END },
]"
v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START"
>
<span>未开始</span>
</button>
<button
:class="[
'ss-reset-button cart-btn',
{ disabled: activeTimeConfig?.status === TimeStatusEnum.END },
]"
@click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"
>
<span>马上抢</span>
</button>
<button
:class="[
'ss-reset-button cart-btn',
{ disabled: activeTimeConfig?.status === TimeStatusEnum.END },
]"
v-else
>
<span>已结束</span>
</button> </button>
</template> </template>
</s-goods-column> </s-goods-column>
@ -100,40 +146,51 @@
</s-layout> </s-layout>
</template> </template>
<script setup> <script setup>
import {reactive, computed, ref, nextTick} from 'vue'; import { reactive, computed, ref, nextTick } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app'; import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep'; import sheep from '@/sheep';
import { useDurationTime } from '@/sheep/hooks/useGoods'; import { useDurationTime } from '@/sheep/hooks/useGoods';
import SeckillApi from "@/sheep/api/promotion/seckill"; import SeckillApi from '@/sheep/api/promotion/seckill';
import dayjs from "dayjs"; import dayjs from 'dayjs';
import {TimeStatusEnum} from "@/sheep/util/const"; import { TimeStatusEnum } from '@/sheep/util/const';
// //
const { safeAreaInsets, safeArea } = sheep.$platform.device; const { safeAreaInsets, safeArea } = sheep.$platform.device;
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const pageHeight = (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350; const pageHeight =
(safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-header.png'); const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-header.png');
// //
const goodsFields = { const goodsFields = {
name: { show: true }, name: {
introduction: { show: true }, show: true,
price: { show: true }, },
marketPrice: { show: true }, introduction: {
show: true,
},
price: {
show: true,
},
marketPrice: {
show: true,
},
}; };
//#region //#region
// //
const timeConfigList = ref([]) const timeConfigList = ref([]);
// //
const getSeckillConfigList = async () => { const getSeckillConfigList = async () => {
const { data } = await SeckillApi.getSeckillConfigList() const { data } = await SeckillApi.getSeckillConfigList();
const now = dayjs(); const now = dayjs();
const today = now.format('YYYY-MM-DD') const today = now.format('YYYY-MM-DD');
const select = ref([]);
// //
data.forEach((config, index) => { data.forEach((config, index) => {
const startTime = dayjs(`${today} ${config.startTime}`) const startTime = dayjs(`${today} ${config.startTime}`);
const endTime = dayjs(`${today} ${config.endTime}`) const endTime = dayjs(`${today} ${config.endTime}`);
select.value[index] = config.id;
if (now.isBefore(startTime)) { if (now.isBefore(startTime)) {
config.status = TimeStatusEnum.WAIT_START; config.status = TimeStatusEnum.WAIT_START;
} else if (now.isAfter(endTime)) { } else if (now.isAfter(endTime)) {
@ -142,35 +199,36 @@
config.status = TimeStatusEnum.STARTED; config.status = TimeStatusEnum.STARTED;
activeTimeIndex.value = index; activeTimeIndex.value = index;
} }
}) });
timeConfigList.value = data timeConfigList.value = data;
// //
handleChangeTimeConfig(activeTimeIndex.value); handleChangeTimeConfig(activeTimeIndex.value, select.value[activeTimeIndex.value]);
// //
scrollToTimeConfig(activeTimeIndex.value) scrollToTimeConfig(activeTimeIndex.value);
} };
// //
const activeTimeElId = ref('') // ID const activeTimeElId = ref(''); // ID
const scrollToTimeConfig = (index) => { const scrollToTimeConfig = (index) => {
nextTick(() => activeTimeElId.value = `timeItem${index}`) nextTick(() => (activeTimeElId.value = `timeItem${index}`));
} };
// //
const activeTimeIndex = ref(0) // const activeTimeIndex = ref(0); //
const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]) // const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]); //
const handleChangeTimeConfig = (index) => { const handleChangeTimeConfig = (index, id) => {
activeTimeIndex.value = index activeTimeIndex.value = index;
// //
activityPageParams.pageNo = 1 activityPageParams.pageNo = 1;
activityList.value = [] activityPageParams.configId = id;
activityList.value = [];
getActivityList(); getActivityList();
} };
// //
const countDown = computed(() => { const countDown = computed(() => {
const endTime = activeTimeConfig.value?.endTime const endTime = activeTimeConfig.value?.endTime;
if (endTime) { if (endTime) {
return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`); return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`);
} }
@ -182,20 +240,22 @@
// //
const activityPageParams = reactive({ const activityPageParams = reactive({
id: 0, // ID configId: 0, // ID
pageNo: 1, // pageNo: 1, //
pageSize: 5, // pageSize: 5, //
}) });
const activityTotal = ref(0) // const activityTotal = ref(0); //
const activityList = ref([]) // const activityList = ref([]); //
const loadStatus = ref('') // const loadStatus = ref(''); //
async function getActivityList() { async function getActivityList() {
loadStatus.value = 'loading'; loadStatus.value = 'loading';
const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams) const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams);
data.list.forEach(activity => { data.list.forEach((activity) => {
// //
activity.percent = parseInt(100 * (activity.totalStock - activity.stock) / activity.totalStock); activity.percent = parseInt(
}) (100 * (activity.totalStock - activity.stock)) / activity.totalStock,
);
});
activityList.value = activityList.value.concat(...data.list); activityList.value = activityList.value.concat(...data.list);
activityTotal.value = data.total; activityTotal.value = data.total;
@ -205,7 +265,7 @@
// //
function loadMore() { function loadMore() {
if (loadStatus.value !== 'noMore') { if (loadStatus.value !== 'noMore') {
activityPageParams.pageNo += 1 activityPageParams.pageNo += 1;
getActivityList(); getActivityList();
} }
} }
@ -216,7 +276,7 @@
// //
onLoad(async () => { onLoad(async () => {
await getSeckillConfigList() await getSeckillConfigList();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -235,7 +295,8 @@
margin: -276rpx auto 0 auto; margin: -276rpx auto 0 auto;
border-radius: 14rpx; border-radius: 14rpx;
overflow: hidden; overflow: hidden;
swiper{
swiper {
height: 330rpx !important; height: 330rpx !important;
border-radius: 14rpx; border-radius: 14rpx;
overflow: hidden; overflow: hidden;
@ -246,7 +307,8 @@
height: 100%; height: 100%;
border-radius: 14rpx; border-radius: 14rpx;
overflow: hidden; overflow: hidden;
img{
img {
border-radius: 14rpx; border-radius: 14rpx;
} }
} }
@ -257,10 +319,12 @@
width: 75rpx; width: 75rpx;
height: 70rpx; height: 70rpx;
} }
// //
.time-list { .time-list {
width: 596rpx; width: 596rpx;
white-space: nowrap; white-space: nowrap;
// //
.item { .item {
display: inline-block; display: inline-block;
@ -270,17 +334,20 @@
box-sizing: border-box; box-sizing: border-box;
margin-right: 30rpx; margin-right: 30rpx;
width: 130rpx; width: 130rpx;
// //
.time { .time {
font-size: 36rpx; font-size: 36rpx;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
} }
// //
&.active { &.active {
.time { .time {
color: var(--ui-BG-Main); color: var(--ui-BG-Main);
} }
// //
.status { .status {
height: 30rpx; height: 30rpx;
@ -301,6 +368,7 @@
margin: 0 20rpx 0 20rpx; margin: 0 20rpx 0 20rpx;
background: #fff; background: #fff;
border-radius: 20rpx 20rpx 0 0; border-radius: 20rpx 20rpx 0 0;
.content-header { .content-header {
width: 100%; width: 100%;
border-radius: 20rpx 20rpx 0 0; border-radius: 20rpx 20rpx 0 0;
@ -312,6 +380,7 @@
height: 64rpx; height: 64rpx;
background: rgba($color: #fff, $alpha: 0.66); background: rgba($color: #fff, $alpha: 0.66);
border-radius: 32px; border-radius: 32px;
// //
.countdown-title { .countdown-title {
font-size: 28rpx; font-size: 28rpx;
@ -319,10 +388,12 @@
color: #333333; color: #333333;
line-height: 28rpx; line-height: 28rpx;
} }
// //
.countdown-time { .countdown-time {
font-size: 28rpx; font-size: 28rpx;
color: rgba(#ed3c30, 0.23); color: rgba(#ed3c30, 0.23);
// //
.countdown-h { .countdown-h {
font-size: 24rpx; font-size: 24rpx;
@ -334,6 +405,7 @@
background: rgba(#ed3c30, 0.23); background: rgba(#ed3c30, 0.23);
border-radius: 6rpx; border-radius: 6rpx;
} }
// //
.countdown-num { .countdown-num {
font-size: 24rpx; font-size: 24rpx;
@ -348,12 +420,15 @@
} }
} }
} }
// //
.scroll-box { .scroll-box {
height: 900rpx; height: 900rpx;
// //
.goods-box { .goods-box {
position: relative; position: relative;
// //
.cart-btn { .cart-btn {
position: absolute; position: absolute;
@ -373,6 +448,7 @@
color: #fff; color: #fff;
} }
} }
// //
.limit { .limit {
font-size: 22rpx; font-size: 22rpx;

View File

@ -36,7 +36,7 @@
<text v-else> <text v-else>
{{ {{
state.coupon.status === 1 state.coupon.status === 1
? '立即使用' ? '使用'
: state.coupon.status === 2 : state.coupon.status === 2
? '已使用' ? '已使用'
: '已过期' : '已过期'

View File

@ -121,7 +121,7 @@
} }
state.id = options.id; state.id = options.id;
const { code, data } = await OrderApi.getOrder(state.id); const { code, data } = await OrderApi.getOrderDetail(state.id);
if (code !== 0) { if (code !== 0) {
sheep.$helper.toast('无待评价订单'); sheep.$helper.toast('无待评价订单');
return; return;

View File

@ -27,10 +27,60 @@
dotCur="bg-mask-40" dotCur="bg-mask-40"
:seizeHeight="750" :seizeHeight="750"
/> />
<!-- 限时折扣/会员价的优惠信息 -->
<view
class="discount"
v-if="
state.settlementSku && state.settlementSku.id && state.settlementSku.promotionPrice
"
>
<image class="disImg" :src="sheep.$url.static('/static/img/shop/goods/dis.png')" />
<view class="discountCont">
<view class="disContT">
<view class="disContT1">
<view class="disContT1P">
{{ fen2yuan(state.settlementSku.promotionPrice) }}
</view>
<view class="disContT1End">
直降
{{ fen2yuan(state.settlementSku.price - state.settlementSku.promotionPrice) }}
</view>
</view>
<view class="disContT2" v-if="state.settlementSku.promotionType === 4">
限时折扣
</view>
<view class="disContT2" v-else-if="state.settlementSku.promotionType === 6">
会员折扣
</view>
</view>
<view class="disContB">
<view class="disContB1">
价格{{ fen2yuan(state.settlementSku.price) }} 剩余
{{ state.settlementSku.stock }}
</view>
<view class="disContB2" v-if="state.settlementSku.promotionEndTime > 0">
距结束仅剩
<countDown
:tipText="' '"
:bgColor="bgColor"
:dayText="':'"
:hourText="':'"
:minuteText="':'"
:secondText="' '"
:datatime="state.settlementSku.promotionEndTime / 1000"
:isDay="false"
/>
</view>
</view>
</view>
</view>
<!-- 价格+标题 --> <!-- 价格+标题 -->
<view class="title-card detail-card ss-p-y-40 ss-p-x-20"> <view class="title-card detail-card ss-p-y-30 ss-p-x-20">
<view class="ss-flex ss-row-between ss-col-center ss-m-b-26"> <!-- 没有限时折扣/会员价的优惠信息时展示的价格信息 -->
<view
class="ss-flex ss-row-between ss-col-center ss-m-b-26"
v-if="!state.settlementSku.promotionPrice"
>
<view class="price-box ss-flex ss-col-bottom"> <view class="price-box ss-flex ss-col-bottom">
<view class="price-text ss-m-r-16"> <view class="price-text ss-m-r-16">
{{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }} {{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
@ -44,24 +94,39 @@
</view> </view>
</view> </view>
<view class="discounts-box ss-flex ss-row-between ss-m-b-28"> <view class="discounts-box ss-flex ss-row-between ss-m-b-28">
<!-- 满减送/限时折扣活动的提示 --> <!-- 查看优惠劵的描述 -->
<view
class="tag ss-m-r-10"
v-for="coupon in state.couponInfo.slice(0, 1)"
:key="coupon.id"
@tap="onOpenActivity"
>
[]{{ fen2yuanSimple(coupon.usePrice) }}{{
coupon.discountType === 1
? '减' + fen2yuanSimple(coupon.discountPrice) + '元'
: '打' + formatDiscountPercent(coupon.discountPercent) + '折'
}}
</view>
<!-- 查看满减送的描述 -->
<div class="tag-content"> <div class="tag-content">
<view class="tag-box ss-flex"> <view class="tag-box ss-flex">
<!-- 最多打印 3 所以需要扣除优惠劵已打印的 -->
<view <view
v-for="item in getRewardActivityRuleItemDescriptions(
state.rewardActivity,
).slice(0, 3 - state.couponInfo.slice(0, 1).length)"
:key="item"
class="tag ss-m-r-10" class="tag ss-m-r-10"
v-for="promos in state.activityInfo" @tap="onOpenActivity"
:key="promos.id"
@tap="onActivity"
> >
{{ promos.name }} <text>{{ item }}</text>
</view> </view>
</view> </view>
</div> </div>
<!-- 领取优惠劵的按钮 -->
<!-- 优惠劵 -->
<view <view
class="get-coupon-box ss-flex ss-col-center ss-m-l-20" class="get-coupon-box ss-flex ss-col-center ss-m-l-20"
@tap="state.showModel = true" @tap="onOpenActivity"
v-if="state.couponInfo.length" v-if="state.couponInfo.length"
> >
<view class="discounts-title ss-m-r-8">领券</view> <view class="discounts-title ss-m-r-8">领券</view>
@ -127,19 +192,12 @@
</view> </view>
</detail-tabbar> </detail-tabbar>
<!-- 优惠劵弹窗 -->
<s-coupon-get
v-model="state.couponInfo"
:show="state.showModel"
@close="state.showModel = false"
@get="onGet"
/>
<!-- 满减送/限时折扣活动弹窗 --> <!-- 满减送/限时折扣活动弹窗 -->
<s-activity-pop <s-activity-pop
v-model="state.activityInfo" v-model="state"
:show="state.showActivityModel" :show="state.showActivityModel"
@close="state.showActivityModel = false" @close="state.showActivityModel = false"
@get="onTakeCoupon"
/> />
</block> </block>
</s-layout> </s-layout>
@ -147,13 +205,21 @@
</template> </template>
<script setup> <script setup>
import { reactive, computed } from 'vue'; import { reactive, computed, ref, toRaw } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app'; import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep'; import sheep from '@/sheep';
import CouponApi from '@/sheep/api/promotion/coupon'; import CouponApi from '@/sheep/api/promotion/coupon';
import ActivityApi from '@/sheep/api/promotion/activity'; import ActivityApi from '@/sheep/api/promotion/activity';
import FavoriteApi from '@/sheep/api/product/favorite'; import FavoriteApi from '@/sheep/api/product/favorite';
import { formatSales, formatGoodsSwiper, fen2yuan } from '@/sheep/hooks/useGoods'; import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
import {
formatSales,
formatGoodsSwiper,
fen2yuan,
fen2yuanSimple,
formatDiscountPercent,
getRewardActivityRuleItemDescriptions,
} from '@/sheep/hooks/useGoods';
import detailNavbar from './components/detail/detail-navbar.vue'; import detailNavbar from './components/detail/detail-navbar.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue'; import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailTabbar from './components/detail/detail-tabbar.vue'; import detailTabbar from './components/detail/detail-tabbar.vue';
@ -165,7 +231,17 @@
import SpuApi from '@/sheep/api/product/spu'; import SpuApi from '@/sheep/api/product/spu';
onPageScroll(() => {}); onPageScroll(() => {});
import countDown from '@/sheep/components/countDown/index.vue';
import OrderApi from '@/sheep/api/trade/order';
import activity from '@/sheep/api/promotion/activity';
const bgColor = {
bgColor: '#E93323',
Color: '#fff',
width: '44rpx',
timeTxtwidth: '16rpx',
isDay: true,
};
const isLogin = computed(() => sheep.$store('user').isLogin); const isLogin = computed(() => sheep.$store('user').isLogin);
const state = reactive({ const state = reactive({
goodsId: 0, goodsId: 0,
@ -173,16 +249,18 @@
goodsInfo: {}, // SPU goodsInfo: {}, // SPU
showSelectSku: false, // SKU showSelectSku: false, // SKU
selectedSku: {}, // SKU selectedSku: {}, // SKU
settlementSku: {}, // SKU selectedSku 使 SKU
showModel: false, // Coupon showModel: false, // Coupon
couponInfo: [], // Coupon couponInfo: [], // Coupon
showActivityModel: false, // / Activity showActivityModel: false, // / Activity
activityInfo: [], // / Activity TODO rewardActivity: {}, //
activityList: [], // // Activity activityList: [], // // Activity
}); });
// //
function onSkuChange(e) { function onSkuChange(e) {
state.selectedSku = e; state.selectedSku = e;
state.settlementSku = e;
} }
// //
@ -196,7 +274,7 @@
// //
function onBuy(e) { function onBuy(e) {
if (!state.selectedSku.id) { if (!e.id) {
sheep.$helper.toast('请选择商品规格'); sheep.$helper.toast('请选择商品规格');
return; return;
} }
@ -213,13 +291,13 @@
}); });
} }
// //
function onActivity() { function onOpenActivity() {
state.showActivityModel = true; state.showActivityModel = true;
} }
// //
async function onGet(id) { async function onTakeCoupon(id) {
const { code } = await CouponApi.takeCoupon(id); const { code } = await CouponApi.takeCoupon(id);
if (code !== 0) { if (code !== 0) {
return; return;
@ -261,6 +339,48 @@
} }
} }
async function getSettlementByIds(ids) {
let { data, code } = await OrderApi.getSettlementProduct(ids);
if (code !== 0 || data.length !== 1) {
return;
}
data = data[0];
// SKU
state.goodsInfo.skus.forEach((sku) => {
data.skus.forEach((item) => {
if (sku.id === item.id) {
sku.promotionType = item.promotionType;
sku.promotionPrice = item.promotionPrice;
sku.promotionId = item.promotionId;
sku.promotionEndTime = item.promotionEndTime;
}
});
});
// promotionPrice
state.settlementSku = state.goodsInfo.skus
.filter((sku) => sku.stock > 0 && sku.promotionPrice > 0)
.reduce((prev, curr) => (prev.promotionPrice < curr.promotionPrice ? prev : curr));
//
if (data.rewardActivity) {
state.rewardActivity = data.rewardActivity;
//
getActivityTime(state.rewardActivity.id);
}
}
//
async function getActivityTime(id) {
const { code, data } = await RewardActivityApi.getRewardActivity(id);
if (code === 0) {
// console.log(' ', data)
state.rewardActivity.startTime = data.startTime;
state.rewardActivity.endTime = data.endTime;
}
}
onLoad((options) => { onLoad((options) => {
// //
if (!options.id) { if (!options.id) {
@ -278,7 +398,6 @@
// //
state.skeletonLoading = false; state.skeletonLoading = false;
state.goodsInfo = res.data; state.goodsInfo = res.data;
// //
if (isLogin.value) { if (isLogin.value) {
FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => { FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => {
@ -293,13 +412,15 @@
// 2. // 2.
getCoupon(); getCoupon();
// 3. // 3.
ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => { ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => {
if (res.code !== 0) { if (res.code !== 0) {
return; return;
} }
state.activityList = res.data; state.activityList = res.data;
}); });
//
getSettlementByIds(state.goodsId);
}); });
</script> </script>
@ -448,4 +569,101 @@
color: #333333; color: #333333;
} }
} }
//
.discount {
width: 750rpx;
height: 100rpx;
// background-color: red;
overflow: hidden;
position: relative;
}
.disImg {
width: 750rpx;
height: 100rpx;
position: absolute;
top: 0;
z-index: -1;
}
.discountCont {
width: 680rpx;
height: 90rpx;
margin: 10rpx auto 0 auto;
// background-color: gold;
}
.disContT {
width: 680rpx;
height: 50rpx;
display: flex;
justify-content: space-between;
}
.disContT1 {
width: 400rpx;
height: 50rpx;
// background-color: green;
display: flex;
justify-content: flex-start;
align-items: center;
}
.disContT2 {
width: 200rpx;
height: 50rpx;
line-height: 50rpx;
// background-color: gold;
font-size: 30rpx;
text-align: end;
color: white;
font-weight: bolder;
font-style: oblique 20deg;
letter-spacing: 0.1rem;
}
.disContT1P {
color: white;
font-weight: bold;
font-size: 28rpx;
}
.disContT1End {
// width: 180rpx;
padding: 0 10rpx;
height: 30rpx;
line-height: 28rpx;
text-align: center;
font-weight: bold;
background-color: white;
color: #ff3000;
font-size: 23rpx;
border-radius: 20rpx;
margin-left: 10rpx;
}
.disContB {
width: 680rpx;
height: 40rpx;
display: flex;
justify-content: space-between;
font-size: 20rpx;
color: white;
align-items: center;
}
.disContB1 {
width: 300rpx;
height: 40rpx;
line-height: 40rpx;
}
.disContB2 {
width: 300rpx;
height: 40rpx;
line-height: 40rpx;
display: flex;
justify-content: flex-end;
}
</style> </style>

View File

@ -118,12 +118,14 @@
</template> </template>
<script setup> <script setup>
import { reactive } from 'vue'; import { reactive, ref } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app'; import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep'; import sheep from '@/sheep';
import _ from 'lodash-es'; import _ from 'lodash-es';
import { resetPagination } from '@/sheep/util'; import { resetPagination } from '@/sheep/util';
import SpuApi from '@/sheep/api/product/spu'; import SpuApi from '@/sheep/api/product/spu';
import OrderApi from '@/sheep/api/trade/order';
import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
const sys_navBar = sheep.$platform.navbar; const sys_navBar = sheep.$platform.navbar;
const emits = defineEmits(['close', 'change']); const emits = defineEmits(['close', 'change']);
@ -277,6 +279,13 @@
if (code !== 0) { if (code !== 0) {
return; return;
} }
//
await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
if (res.code !== 0) {
return;
}
appendSettlementProduct(data.list, res.data);
});
state.pagination.list = _.concat(state.pagination.list, data.list); state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total; state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';

525
pages/goods/point.vue Normal file
View File

@ -0,0 +1,525 @@
<!-- 秒杀商品详情 -->
<template>
<s-layout :onShareAppMessage="shareInfo" navbar="goods">
<!-- 标题栏 -->
<detailNavbar />
<!-- 骨架屏 -->
<detailSkeleton v-if="state.skeletonLoading" />
<!-- 下架/售罄提醒 -->
<s-empty
v-else-if="state.goodsInfo === null || state.goodsInfo.activity_type !== PromotionActivityTypeEnum.POINT.type"
text="活动不存在或已结束"
icon="/static/soldout-empty.png"
showAction
actionText="再逛逛"
actionUrl="/pages/goods/list"
/>
<block v-else>
<view class="detail-swiper-selector">
<!-- 商品图轮播 -->
<su-swiper
class="ss-m-b-14"
isPreview
:list="state.goodsSwiper"
dotStyle="tag"
imageMode="widthFix"
dotCur="bg-mask-40"
:seizeHeight="750"
/>
<!-- 价格+标题 -->
<view class="title-card ss-m-y-14 ss-m-x-20 ss-p-x-20 ss-p-y-34">
<view class="price-box ss-flex ss-row-between ss-m-b-18">
<view class="ss-flex">
<view class="price-text ss-m-r-16">
{{ getShowPriceText }}
</view>
<view class="tig ss-flex ss-col-center">
<view class="tig-icon ss-flex ss-col-center ss-row-center">
<text class="cicon-alarm"></text>
</view>
<view class="tig-title">积分价</view>
</view>
</view>
</view>
<view class="ss-flex ss-row-between ss-m-b-60">
<view class="origin-price ss-flex ss-col-center" v-if="state.goodsInfo.marketPrice">
原价
<view class="origin-price-text">
{{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
</view>
</view>
</view>
<view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name || '' }}</view>
<view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view>
</view>
<!-- 功能卡片 -->
<view class="detail-cell-card detail-card ss-flex-col">
<detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" />
</view>
<!-- 规格与数量弹框 -->
<s-select-seckill-sku
v-model="state.goodsInfo"
:show="state.showSelectSku"
:single-limit-count="activity.singleLimitCount"
@buy="onBuy"
@change="onSkuChange"
@close="state.showSelectSku = false"
/>
</view>
<!-- 评价 -->
<detail-comment-card class="detail-comment-selector" :goodsId="state.goodsInfo.id" />
<!-- 详情 -->
<detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" />
<!-- 详情tabbar -->
<detail-tabbar v-model="state.goodsInfo">
<view class="buy-box ss-flex ss-col-center ss-p-r-20">
<button
class="ss-reset-button origin-price-btn ss-flex-col"
v-if="state.goodsInfo.marketPrice"
@tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })"
>
<view>
<view class="btn-price">{{ fen2yuan(state.goodsInfo.marketPrice) }}</view>
<view>原价购买</view>
</view>
</button>
<button
class="ss-reset-button btn-box ss-flex-col"
@tap="state.showSelectSku = true"
:class="
state.goodsInfo.stock != 0
? 'check-btn-box'
: 'disabled-btn-box'
"
:disabled="state.goodsInfo.stock === 0"
>
<view class="price-text">
{{getShowPriceText}}
</view>
<view v-if="state.goodsInfo.stock === 0"></view>
<view v-else></view>
</button>
</view>
</detail-tabbar>
</block>
</s-layout>
</template>
<script setup>
import { computed, reactive, ref, unref } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash-es';
import { fen2yuan, formatGoodsSwiper } from '@/sheep/hooks/useGoods';
import detailNavbar from './components/detail/detail-navbar.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailTabbar from './components/detail/detail-tabbar.vue';
import detailSkeleton from './components/detail/detail-skeleton.vue';
import detailCommentCard from './components/detail/detail-comment-card.vue';
import detailContentCard from './components/detail/detail-content-card.vue';
import SpuApi from '@/sheep/api/product/spu';
import { PromotionActivityTypeEnum } from '@/sheep/util/const';
import PointApi from '@/sheep/api/promotion/point';
const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-bg.png');
const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png');
const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png');
const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png');
const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
onPageScroll(() => {
});
const state = reactive({
skeletonLoading: true,
goodsInfo: {},
showSelectSku: false,
goodsSwiper: [],
selectedSku: {},
showModel: false,
total: 0,
price: '',
});
//
function onSkuChange(e) {
state.selectedSku = e;
}
//
function onBuy(sku) {
sheep.$router.go('/pages/order/confirm', {
data: JSON.stringify({
order_type: 'goods',
buy_type: 'point',
pointActivityId: activity.value.id,
items: [
{
skuId: sku.id,
count: sku.count,
},
],
}),
});
}
//
// TODO puhui999: fix
const shareInfo = computed(() => {
if (isEmpty(unref(activity))) return {};
return sheep.$platform.share.getShareInfo(
{
title: activity.value.name,
image: sheep.$url.cdn(state.goodsInfo.picUrl),
params: {
page: '4',
query: activity.value.id,
},
},
{
type: 'goods', //
title: activity.value.name, //
image: sheep.$url.cdn(state.goodsInfo.picUrl), //
price: state.goodsInfo.price, //
marketPrice: state.goodsInfo.marketPrice, //
},
);
});
const activity = ref();
const getShowPriceText = computed(() => {
let priceText = `${activity.value.point}积分${!activity.value.price ? '' : `+¥${fen2yuan(activity.value.price)}`}`;
if (!isEmpty(state.selectedSku)) {
const sku = state.selectedSku;
priceText = `${sku.point}积分${!sku.pointPrice ? '' : `+¥${fen2yuan(sku.pointPrice)}`}`;
}
return priceText;
});
//
const getActivity = async (id) => {
const { data } = await PointApi.getPointActivity(id);
activity.value = data;
//
await getSpu(data.spuId);
};
//
const getSpu = async (id) => {
const { data } = await SpuApi.getSpuDetail(id);
data.activity_type = PromotionActivityTypeEnum.POINT.type;
state.goodsInfo = data;
state.goodsInfo.stock = Math.min(data.stock, activity.value.stock);
//
state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls);
// 使
data.skus.forEach((sku) => {
const product = activity.value.products.find((product) => product.skuId === sku.id);
if (product) {
sku.point = product.point;
sku.pointPrice = product.price;
sku.stock = Math.min(sku.stock, product.stock);
//
sku.limitCount = product.count;
} else {
//
sku.stock = 0;
}
});
state.skeletonLoading = false;
};
onLoad((options) => {
//
if (!options.id) {
state.goodsInfo = null;
return;
}
//
getActivity(options.id);
});
</script>
<style lang="scss" scoped>
.disabled-btn-box[disabled] {
background-color: transparent;
}
.detail-card {
background-color: $white;
margin: 14rpx 20rpx;
border-radius: 10rpx;
overflow: hidden;
}
//
.title-card {
width: 710rpx;
box-sizing: border-box;
// height: 320rpx;
background-size: 100% 100%;
border-radius: 10rpx;
background-image: v-bind(headerBg);
background-repeat: no-repeat;
.price-box {
.price-text {
font-size: 30rpx;
font-weight: 500;
color: #fff;
line-height: normal;
font-family: OPPOSANS;
}
}
.origin-price {
font-size: 24rpx;
font-weight: 400;
color: #fff;
opacity: 0.7;
.origin-price-text {
text-decoration: line-through;
font-family: OPPOSANS;
&::before {
content: '¥';
}
}
}
.tig {
border: 2rpx solid #ffffff;
border-radius: 4rpx;
width: 126rpx;
height: 38rpx;
.tig-icon {
width: 40rpx;
height: 40rpx;
margin-left: -2rpx;
background: #ffffff;
border-radius: 4rpx 0 0 4rpx;
.cicon-alarm {
font-size: 32rpx;
color: #fc6e6f;
}
}
.tig-title {
width: 86rpx;
font-size: 24rpx;
font-weight: 500;
line-height: normal;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
}
}
.countdown-title {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
.countdown-time {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
.countdown-h {
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #ffffff;
padding: 0 4rpx;
height: 40rpx;
background: rgba(#000000, 0.1);
border-radius: 6rpx;
}
.countdown-num {
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #ffffff;
width: 40rpx;
height: 40rpx;
background: rgba(#000000, 0.1);
border-radius: 6rpx;
}
}
.discounts-box {
.discounts-tag {
padding: 4rpx 10rpx;
font-size: 24rpx;
font-weight: 500;
border-radius: 4rpx;
color: var(--ui-BG-Main);
// background: rgba(#2aae67, 0.05);
background: var(--ui-BG-Main-tag);
}
.discounts-title {
font-size: 24rpx;
font-weight: 500;
color: var(--ui-BG-Main);
line-height: normal;
}
.cicon-forward {
color: var(--ui-BG-Main);
font-size: 24rpx;
line-height: normal;
margin-top: 4rpx;
}
}
.title-text {
font-size: 30rpx;
font-weight: bold;
line-height: 42rpx;
color: #fff;
}
.subtitle-text {
font-size: 26rpx;
font-weight: 400;
color: #ffffff;
line-height: 42rpx;
opacity: 0.9;
}
}
//
.buy-box {
.check-btn-box {
width: 248rpx;
height: 80rpx;
font-size: 24rpx;
font-weight: 600;
margin-left: -36rpx;
background-image: v-bind(btnBg);
background-repeat: no-repeat;
background-size: 100% 100%;
color: #ffffff;
line-height: normal;
border-radius: 0px 40rpx 40rpx 0px;
}
.disabled-btn-box {
width: 248rpx;
height: 80rpx;
font-size: 24rpx;
font-weight: 600;
margin-left: -36rpx;
background-image: v-bind(disabledBtnBg);
background-repeat: no-repeat;
background-size: 100% 100%;
color: #999999;
line-height: normal;
border-radius: 0px 40rpx 40rpx 0px;
}
.btn-price {
font-family: OPPOSANS;
&::before {
content: '¥';
}
}
.origin-price-btn {
width: 236rpx;
height: 80rpx;
background: rgba(#ff5651, 0.1);
color: #ff6000;
border-radius: 40rpx 0px 0px 40rpx;
line-height: normal;
font-size: 24rpx;
font-weight: 500;
.no-original {
font-size: 28rpx;
}
.btn-title {
font-size: 28rpx;
}
}
}
//
.seckill-box {
background: v-bind(seckillBg) no-repeat;
background-size: 100% 100%;
}
.groupon-box {
background: v-bind(grouponBg) no-repeat;
background-size: 100% 100%;
}
//
.activity-box {
width: 100%;
height: 80rpx;
box-sizing: border-box;
margin-bottom: 10rpx;
.activity-title {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
line-height: 42rpx;
.activity-icon {
width: 38rpx;
height: 38rpx;
}
}
.activity-go {
width: 70rpx;
height: 32rpx;
background: #ffffff;
border-radius: 16rpx;
font-weight: 500;
color: #ff6000;
font-size: 24rpx;
line-height: normal;
}
}
.model-box {
.title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
}
.subtitle {
font-size: 26rpx;
font-weight: 500;
color: #333333;
}
}
image {
width: 100%;
height: 100%;
}
</style>

View File

@ -225,8 +225,8 @@
const getActivity = async (id) => { const getActivity = async (id) => {
const { data } = await SeckillApi.getSeckillActivity(id); const { data } = await SeckillApi.getSeckillActivity(id);
activity.value = data; activity.value = data;
timeStatusEnum.value = getTimeStatusEnum(activity.startTime, activity.endTime); timeStatusEnum.value = getTimeStatusEnum(activity.value.startTime, activity.value.endTime);
state.percent = 100 - (data.stock / data.totalStock) * 100;
// //
await getSpu(data.spuId); await getSpu(data.spuId);
}; };
@ -284,6 +284,7 @@
.disabled-btn-box[disabled] { .disabled-btn-box[disabled] {
background-color: transparent; background-color: transparent;
} }
.detail-card { .detail-card {
background-color: $white; background-color: $white;
margin: 14rpx 20rpx; margin: 14rpx 20rpx;
@ -374,6 +375,7 @@
font-size: 26rpx; font-size: 26rpx;
font-weight: 500; font-weight: 500;
color: #ffffff; color: #ffffff;
.countdown-h { .countdown-h {
font-size: 24rpx; font-size: 24rpx;
font-family: OPPOSANS; font-family: OPPOSANS;
@ -384,6 +386,7 @@
background: rgba(#000000, 0.1); background: rgba(#000000, 0.1);
border-radius: 6rpx; border-radius: 6rpx;
} }
.countdown-num { .countdown-num {
font-size: 24rpx; font-size: 24rpx;
font-family: OPPOSANS; font-family: OPPOSANS;
@ -467,6 +470,7 @@
line-height: normal; line-height: normal;
border-radius: 0px 40rpx 40rpx 0px; border-radius: 0px 40rpx 40rpx 0px;
} }
.btn-price { .btn-price {
font-family: OPPOSANS; font-family: OPPOSANS;
@ -484,6 +488,7 @@
line-height: normal; line-height: normal;
font-size: 24rpx; font-size: 24rpx;
font-weight: 500; font-weight: 500;
.no-original { .no-original {
font-size: 28rpx; font-size: 28rpx;
} }

View File

@ -208,7 +208,7 @@
state.itemId = parseInt(options.itemId); state.itemId = parseInt(options.itemId);
// //
const { code, data } = await OrderApi.getOrder(state.orderId); const { code, data } = await OrderApi.getOrderDetail(state.orderId);
if (code !== 0) { if (code !== 0) {
return; return;
} }

View File

@ -143,8 +143,7 @@
v-if="state.orderInfo.price.discountPrice > 0" v-if="state.orderInfo.price.discountPrice > 0"
> >
<view class="item-title">活动优惠</view> <view class="item-title">活动优惠</view>
<view class="ss-flex ss-col-center"> <view class="ss-flex ss-col-center" @tap="state.showDiscount = true">
<!-- @tap="state.showDiscount = true" TODO puhui999折扣后续要把优惠信息打进去 -->
<text class="item-value text-red"> <text class="item-value text-red">
-{{ fen2yuan(state.orderInfo.price.discountPrice) }} -{{ fen2yuan(state.orderInfo.price.discountPrice) }}
</text> </text>
@ -295,6 +294,7 @@
combinationActivityId: state.orderPayload.combinationActivityId, combinationActivityId: state.orderPayload.combinationActivityId,
combinationHeadId: state.orderPayload.combinationHeadId, combinationHeadId: state.orderPayload.combinationHeadId,
seckillActivityId: state.orderPayload.seckillActivityId, seckillActivityId: state.orderPayload.seckillActivityId,
pointActivityId: state.orderPayload.pointActivityId,
}); });
if (code !== 0) { if (code !== 0) {
return; return;
@ -325,6 +325,7 @@
combinationActivityId: state.orderPayload.combinationActivityId, combinationActivityId: state.orderPayload.combinationActivityId,
combinationHeadId: state.orderPayload.combinationHeadId, combinationHeadId: state.orderPayload.combinationHeadId,
seckillActivityId: state.orderPayload.seckillActivityId, seckillActivityId: state.orderPayload.seckillActivityId,
pointActivityId: state.orderPayload.pointActivityId,
}); });
if (code !== 0) { if (code !== 0) {
return; return;

View File

@ -127,7 +127,11 @@
</view> </view>
<!-- 自提核销 --> <!-- 自提核销 -->
<PickUpVerify :order-info="state.orderInfo" :systemStore="systemStore" ref="pickUpVerifyRef"></PickUpVerify> <PickUpVerify
:order-info="state.orderInfo"
:systemStore="systemStore"
ref="pickUpVerifyRef"
></PickUpVerify>
<!-- 订单信息 --> <!-- 订单信息 -->
<view class="notice-box"> <view class="notice-box">
@ -305,7 +309,7 @@
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '确定要取消订单吗?', content: '确定要取消订单吗?',
success: async function(res) { success: async function (res) {
if (!res.confirm) { if (!res.confirm) {
return; return;
} }
@ -394,11 +398,11 @@
let res; let res;
if (state.comeinType === 'wechat') { if (state.comeinType === 'wechat') {
// TODO // TODO
res = await OrderApi.getOrder(id, { res = await OrderApi.getOrderDetail(id, {
merchant_trade_no: state.merchantTradeNo, merchant_trade_no: state.merchantTradeNo,
}); });
} else { } else {
res = await OrderApi.getOrder(id); res = await OrderApi.getOrderDetail(id);
} }
if (res.code === 0) { if (res.code === 0) {
state.orderInfo = res.data; state.orderInfo = res.data;
@ -451,7 +455,7 @@
color: rgba(#fff, 0.9); color: rgba(#fff, 0.9);
width: 100%; width: 100%;
background: v-bind(headerBg) no-repeat, background: v-bind(headerBg) no-repeat,
linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
background-size: 750rpx 100%; background-size: 750rpx 100%;
box-sizing: border-box; box-sizing: border-box;

View File

@ -13,10 +13,10 @@
</uni-swiper-dot> </uni-swiper-dot>
<view class="log-card-msg"> <view class="log-card-msg">
<!-- TODO 芋艿物流优化点展示状态 --> <!-- TODO 芋艿物流优化点展示状态 -->
<!-- <view class="ss-flex ss-m-b-8">--> <!-- <view class="ss-flex ss-m-b-8">-->
<!-- <view>物流状态</view>--> <!-- <view>物流状态</view>-->
<!-- <view class="warning-color">{{ state.info.status_text }}</view>--> <!-- <view class="warning-color">{{ state.info.status_text }}</view>-->
<!-- </view>--> <!-- </view>-->
<view class="ss-m-b-8">快递单号{{ state.info.logisticsNo }}</view> <view class="ss-m-b-8">快递单号{{ state.info.logisticsNo }}</view>
<view>快递公司{{ state.info.logisticsName }}</view> <view>快递公司{{ state.info.logisticsName }}</view>
</view> </view>
@ -35,9 +35,9 @@
</view> </view>
<view class="log-content-msg"> <view class="log-content-msg">
<!-- TODO 芋艿物流优化点展示状态 --> <!-- TODO 芋艿物流优化点展示状态 -->
<!-- <view class="log-msg-title ss-m-b-20">--> <!-- <view class="log-msg-title ss-m-b-20">-->
<!-- {{ item.status_text }}--> <!-- {{ item.status_text }}-->
<!-- </view>--> <!-- </view>-->
<view class="log-msg-desc ss-m-b-16">{{ item.content }}</view> <view class="log-msg-desc ss-m-b-16">{{ item.content }}</view>
<view class="log-msg-date ss-m-b-40"> <view class="log-msg-date ss-m-b-40">
{{ sheep.$helper.timeFormat(item.time, 'yyyy-mm-dd hh:MM:ss') }} {{ sheep.$helper.timeFormat(item.time, 'yyyy-mm-dd hh:MM:ss') }}
@ -78,7 +78,7 @@
} }
async function getOrderDetail(id) { async function getOrderDetail(id) {
const { data } = await OrderApi.getOrder(id) const { data } = await OrderApi.getOrderDetail(id);
state.info = data; state.info = data;
} }

View File

@ -19,15 +19,15 @@
<view class="num">{{ orderInfo.pickUpVerifyCode }}</view> <view class="num">{{ orderInfo.pickUpVerifyCode }}</view>
<view class="rules"> <view class="rules">
<!-- TODO puhui999: 需要后端放回使用 receiveTime 即可 --> <!-- TODO puhui999: 需要后端放回使用 receiveTime 即可 -->
<!-- <view class="item">--> <view class="item">
<!-- <view class="rulesTitle flex flex-wrap align-center">--> <view class="rulesTitle flex flex-wrap align-center">
<!-- 核销时间--> 核销时间
<!-- </view>--> </view>
<!-- <view class="info">--> <view class="info">
<!-- 每日--> 每日
<!-- <text class="time">2020-2-+52</text>--> <text class="time">2020-2-+52</text>
<!-- </view>--> </view>
<!-- </view>--> </view>
<view class="item"> <view class="item">
<view class="rulesTitle flex flex-wrap align-center"> <view class="rulesTitle flex flex-wrap align-center">
<text class="iconfont icon-shuoming1"></text> <text class="iconfont icon-shuoming1"></text>
@ -138,7 +138,6 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// TODO puhui999: bug
.borRadius14 { .borRadius14 {
border-radius: 14rpx !important; border-radius: 14rpx !important;
} }

View File

@ -81,7 +81,7 @@
import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods'; import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods';
import PayOrderApi from '@/sheep/api/pay/order'; import PayOrderApi from '@/sheep/api/pay/order';
import PayChannelApi from '@/sheep/api/pay/channel'; import PayChannelApi from '@/sheep/api/pay/channel';
import { getPayMethods } from '@/sheep/platform/pay'; import { getPayMethods, goPayResult } from '@/sheep/platform/pay';
const userWallet = computed(() => sheep.$store('user').userWallet); const userWallet = computed(() => sheep.$store('user').userWallet);
@ -135,12 +135,22 @@
// payOrder.status => payStatus // payOrder.status => payStatus
function checkPayStatus() { function checkPayStatus() {
if (state.orderInfo.status === 10 if (state.orderInfo.status === 10 || state.orderInfo.status === 20) {
|| state.orderInfo.status === 20 ) { // //
state.payStatus = 2; state.payStatus = 2;
//
uni.showModal({
title: '提示',
content: '订单已支付',
showCancel: false,
success: function () {
goPayResult(state.orderInfo.id, state.orderType);
},
});
return; return;
} }
if (state.orderInfo.status === 30) { // if (state.orderInfo.status === 30) {
//
state.payStatus = -1; state.payStatus = -1;
return; return;
} }
@ -155,26 +165,26 @@
// //
async function setOrder(id) { async function setOrder(id) {
// //
const { data, code } = await PayOrderApi.getOrder(id); const { data, code } = await PayOrderApi.getOrder(id, true);
if (code !== 0 || !data) { if (code !== 0 || !data) {
state.payStatus = -2; state.payStatus = -2;
return; return;
} }
state.orderInfo = data; state.orderInfo = data;
//
await setPayMethods();
// //
checkPayStatus(); checkPayStatus();
//
await setPayMethods();
} }
// //
async function setPayMethods() { async function setPayMethods() {
const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId) const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId);
if (code !== 0) { if (code !== 0) {
return return;
} }
state.payMethods = getPayMethods(data) state.payMethods = getPayMethods(data);
state.payMethods.find(item => { state.payMethods.find((item) => {
if (item.value && !item.disabled) { if (item.value && !item.disabled) {
state.payment = item.value; state.payment = item.value;
return true; return true;
@ -183,9 +193,11 @@
} }
onLoad((options) => { onLoad((options) => {
if (sheep.$platform.name === 'WechatOfficialAccount' if (
&& sheep.$platform.os === 'ios' sheep.$platform.name === 'WechatOfficialAccount' &&
&& !sheep.$platform.landingPage.includes('pages/pay/index')) { sheep.$platform.os === 'ios' &&
!sheep.$platform.landingPage.includes('pages/pay/index')
) {
location.reload(); location.reload();
return; return;
} }
@ -214,7 +226,6 @@
position: relative; position: relative;
padding: 60rpx 20rpx 40rpx; padding: 60rpx 20rpx 40rpx;
.money-text { .money-text {
color: $red; color: $red;
font-size: 46rpx; font-size: 46rpx;

View File

@ -126,7 +126,10 @@
// #endif // #endif
// //
if (state.orderType === 'goods') { if (state.orderType === 'goods') {
const { data, code } = await OrderApi.getOrder(state.orderInfo.merchantOrderId); const { data, code } = await OrderApi.getOrderDetail(
state.orderInfo.merchantOrderId,
true,
);
if (code === 0) { if (code === 0) {
state.tradeOrder = data; state.tradeOrder = data;
} }

View File

@ -2,11 +2,11 @@ import request from '@/sheep/request';
const PayOrderApi = { const PayOrderApi = {
// 获得支付订单 // 获得支付订单
getOrder: (id) => { getOrder: (id, sync) => {
return request({ return request({
url: '/pay/order/get', url: '/pay/order/get',
method: 'GET', method: 'GET',
params: { id } params: { id, sync },
}); });
}, },
// 提交支付订单 // 提交支付订单
@ -14,9 +14,9 @@ const PayOrderApi = {
return request({ return request({
url: '/pay/order/submit', url: '/pay/order/submit',
method: 'POST', method: 'POST',
data data,
}); });
} },
}; };
export default PayOrderApi; export default PayOrderApi;

View File

@ -13,6 +13,18 @@ const SpuApi = {
}, },
}); });
}, },
// 获得商品结算信息
getSettlementProduct: (spuIds) => {
return request({
url: '/trade/order/settlement-product',
method: 'GET',
params: { spuIds },
custom: {
showLoading: false,
showError: false,
},
});
},
// 获得商品 SPU 分页 // 获得商品 SPU 分页
getSpuPage: (params) => { getSpuPage: (params) => {
return request({ return request({

View File

@ -0,0 +1,30 @@
import request from '@/sheep/request';
const PointApi = {
// 获得积分商城活动分页
getPointActivityPage: (params) => {
return request({ url: 'promotion/point-activity/page', method: 'GET', params });
},
// 获得积分商城活动列表,基于活动编号数组
getPointActivityListByIds: (ids) => {
return request({
url: '/promotion/point-activity/list-by-ids',
method: 'GET',
params: {
ids,
},
});
},
// 获得积分商城活动明细
getPointActivity: (id) => {
return request({
url: 'promotion/point-activity/get-detail',
method: 'GET',
params: { id },
});
},
};
export default PointApi;

View File

@ -53,6 +53,18 @@ const OrderApi = {
}, },
}); });
}, },
// 获得商品结算信息
getSettlementProduct: (spuIds) => {
return request({
url: '/trade/order/settlement-product',
method: 'GET',
params: { spuIds },
custom: {
showLoading: false,
showError: false,
},
});
},
// 创建订单 // 创建订单
createOrder: (data) => { createOrder: (data) => {
return request({ return request({
@ -61,13 +73,14 @@ const OrderApi = {
data, data,
}); });
}, },
// 获得订单 // 获得订单详细sync 是可选参数
getOrder: (id) => { getOrderDetail: (id, sync) => {
return request({ return request({
url: `/trade/order/get-detail`, url: `/trade/order/get-detail`,
method: 'GET', method: 'GET',
params: { params: {
id, id,
sync,
}, },
custom: { custom: {
showLoading: false, showLoading: false,

View File

@ -0,0 +1,196 @@
<!-- TODO 是不是怎么复用 s-count-down 组件 -->
<template>
<view class="time" :style="justifyLeft">
<text class="" v-if="tipText">{{ tipText }}</text>
<text
class="styleAll p6"
v-if="isDay === true"
:style="{ background: bgColor.bgColor, color: bgColor.Color }"
>{{ day }}{{ bgColor.isDay ? '天' : '' }}</text
>
<text
class="timeTxt"
v-if="dayText"
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
>{{ dayText }}</text
>
<text
class="styleAll"
:class="isCol ? 'timeCol' : ''"
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
>{{ hour }}</text
>
<text
class="timeTxt"
v-if="hourText"
:class="isCol ? 'whit' : ''"
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
>{{ hourText }}</text
>
<text
class="styleAll"
:class="isCol ? 'timeCol' : ''"
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
>{{ minute }}</text
>
<text
class="timeTxt"
v-if="minuteText"
:class="isCol ? 'whit' : ''"
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
>{{ minuteText }}</text
>
<text
class="styleAll"
:class="isCol ? 'timeCol' : ''"
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
>{{ second }}</text
>
<text class="timeTxt" v-if="secondText">{{ secondText }}</text>
</view>
</template>
<script>
export default {
name: 'countDown',
props: {
justifyLeft: {
type: String,
default: '',
},
//
tipText: {
type: String,
default: '倒计时',
},
dayText: {
type: String,
default: '天',
},
hourText: {
type: String,
default: '时',
},
minuteText: {
type: String,
default: '分',
},
secondText: {
type: String,
default: '秒',
},
datatime: {
type: Number,
default: 0,
},
isDay: {
type: Boolean,
default: true,
},
isCol: {
type: Boolean,
default: false,
},
bgColor: {
type: Object,
default: null,
},
},
data: function () {
return {
day: '00',
hour: '00',
minute: '00',
second: '00',
};
},
created: function () {
this.show_time();
},
mounted: function () {},
methods: {
show_time: function () {
let that = this;
function runTime() {
//
let intDiff = that.datatime - Date.parse(new Date()) / 1000; //
let day = 0,
hour = 0,
minute = 0,
second = 0;
if (intDiff > 0) {
//
if (that.isDay === true) {
day = Math.floor(intDiff / (60 * 60 * 24));
} else {
day = 0;
}
hour = Math.floor(intDiff / (60 * 60)) - day * 24;
minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60;
second = Math.floor(intDiff) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
if (hour <= 9) hour = '0' + hour;
if (minute <= 9) minute = '0' + minute;
if (second <= 9) second = '0' + second;
that.day = day;
that.hour = hour;
that.minute = minute;
that.second = second;
} else {
that.day = '00';
that.hour = '00';
that.minute = '00';
that.second = '00';
}
}
runTime();
setInterval(runTime, 1000);
},
},
};
</script>
<style scoped>
.p6 {
padding: 0 8rpx;
}
.styleAll {
/* color: #fff; */
font-size: 24rpx;
height: 36rpx;
line-height: 36rpx;
border-radius: 6rpx;
text-align: center;
/* padding: 0 6rpx; */
}
.timeTxt {
text-align: center;
/* width: 16rpx; */
height: 36rpx;
line-height: 36rpx;
display: inline-block;
}
.whit {
color: #fff !important;
}
.time {
display: flex;
justify-content: center;
}
.red {
color: #fc4141;
margin: 0 4rpx;
}
.timeCol {
/* width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align:center;
border-radius: 6px;
background: #fff;
font-size: 24rpx; */
color: #e93323;
}
</style>

View File

@ -2,34 +2,81 @@
<template> <template>
<su-popup :show="show" type="bottom" round="20" @close="emits('close')" showClose> <su-popup :show="show" type="bottom" round="20" @close="emits('close')" showClose>
<view class="model-box"> <view class="model-box">
<view class="title ss-m-t-16 ss-m-l-20 ss-flex">营销活动</view> <view class="title ss-m-t-16 ss-m-l-20 ss-flex">优惠</view>
<view v-if="state.rewardActivity && state.rewardActivity.id > 0">
<view class="titleLi">促销</view>
<scroll-view
class="model-content"
scroll-y
:scroll-with-animation="false"
:enable-back-to-top="true"
>
<view
class="actBox"
v-for="(item, index) in getRewardActivityRuleGroupDescriptions(state.rewardActivity)"
:key="index"
>
<view
class="boxCont ss-flex ss-col-top ss-m-b-40"
@tap="onGoodsList(state.rewardActivity)"
>
<view class="model-content-tag ss-flex ss-row-center">{{ item.name }}</view>
<view class="model-content-title">
<view class="contBu">
{{ item.values.join(';') }}
</view>
<view class="ss-m-b-24 cotBu-txt">
{{ sheep.$helper.timeFormat(state.rewardActivity.startTime, 'yyyy.mm.dd') }}
-
{{ sheep.$helper.timeFormat(state.rewardActivity.endTime, 'yyyy.mm.dd') }}
</view>
</view>
<text class="cicon-forward" />
</view>
</view>
</scroll-view>
</view>
<view class="titleLi">可领优惠券</view>
<scroll-view <scroll-view
class="model-content ss-m-t-50" class="model-content"
scroll-y scroll-y
:scroll-with-animation="false" :scroll-with-animation="false"
:enable-back-to-top="true" :enable-back-to-top="true"
v-if="state.couponInfo.length"
> >
<view v-for="item in state.activityInfo" :key="item.id"> <view class="actBox" v-for="item in state.couponInfo" :key="item.id">
<view class="ss-flex ss-col-top ss-m-b-40" @tap="onGoodsList(item)"> <view class="boxCont ss-flex ss-col-top ss-m-b-40">
<view class="model-content-tag ss-flex ss-row-center">满减</view> <view class="model-content-tag2">
<view class="ss-m-l-20 model-content-title ss-flex-1"> <view class="usePrice"> {{ fen2yuan(item.discountPrice) }} </view>
<view class="ss-m-b-24" v-for="rule in state.activityMap[item.id]?.rules" :key="rule"> <view class="impose"> {{ fen2yuan(item.usePrice) }}可用 </view>
{{ formatRewardActivityRule(state.activityMap[item.id], rule) }} </view>
<view class="model-content-title2">
<view class="contBu">
{{ item.name }}
</view>
<view class="ss-m-b-24 cotBu-txt">
{{
item.validityType == 1
? sheep.$helper.timeFormat(item.validStartTime, 'yyyy.mm.dd') -
sheep.$helper.timeFormat(item.validEndTime, 'yyyy.mm.dd')
: '领取后' + item.fixedStartTerm + '-' + item.fixedEndTerm + '天可用'
}}
</view> </view>
</view> </view>
<text class="cicon-forward" /> <view class="coupon" @click.stop="getBuy(item.id)" v-if="item.canTake"> </view>
<view class="coupon2" v-else> </view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view class="nullBox" v-else> </view>
</view> </view>
</su-popup> </su-popup>
</template> </template>
<script setup> <script setup>
import sheep from '@/sheep'; import sheep from '@/sheep';
import { computed, reactive, watch } from 'vue'; import { getRewardActivityRuleGroupDescriptions } from '@/sheep/hooks/useGoods';
import RewardActivityApi from '@/sheep/api/promotion/rewardActivity'; import { computed, reactive, watch, ref } from 'vue';
import { formatRewardActivityRule } from '@/sheep/hooks/useGoods'; import { fen2yuan } from '@/sheep/hooks/useGoods';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
@ -42,26 +89,14 @@
}); });
const emits = defineEmits(['close']); const emits = defineEmits(['close']);
const state = reactive({ const state = reactive({
activityInfo: computed(() => props.modelValue), rewardActivity: computed(() => props.modelValue.rewardActivity),
activityMap: {} couponInfo: computed(() => props.modelValue.couponInfo),
}); });
watch( //
() => props.show, const getBuy = (id) => {
() => { emits('get', id);
// };
if (props.show) {
state.activityInfo?.forEach(activity => {
RewardActivityApi.getRewardActivity(activity.id).then(res => {
if (res.code !== 0) {
return;
}
state.activityMap[activity.id] = res.data;
})
});
}
},
);
function onGoodsList(e) { function onGoodsList(e) {
sheep.$router.go('/pages/activity/index', { sheep.$router.go('/pages/activity/index', {
@ -72,34 +107,149 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.model-box { .model-box {
height: 60vh; height: 60vh;
.title { .title {
justify-content: center;
font-size: 36rpx; font-size: 36rpx;
height: 80rpx; height: 80rpx;
font-weight: bold; font-weight: bold;
color: #333333; color: #333333;
} }
} }
.model-content { .model-content {
height: fit-content;
max-height: 380rpx;
padding: 0 20rpx; padding: 0 20rpx;
box-sizing: border-box; box-sizing: border-box;
margin-top: 20rpx;
.model-content-tag { .model-content-tag {
background: rgba(#ff6911, 0.1); // background: rgba(#ff6911, 0.1);
font-size: 24rpx; font-size: 35rpx;
font-weight: 500; font-weight: 500;
color: #ff6911; color: #ff6911;
line-height: 42rpx; line-height: 150rpx;
width: 68rpx; width: 200rpx;
height: 32rpx; height: 150rpx;
border-radius: 5rpx; text-align: center;
// border-radius: 5rpx;
} }
.model-content-title { .model-content-title {
width: 450rpx;
height: 150rpx;
font-size: 26rpx; font-size: 26rpx;
font-weight: 500; font-weight: 500;
color: #333333; color: #333333;
overflow: hidden;
} }
.cicon-forward { .cicon-forward {
font-size: 28rpx; font-size: 28rpx;
color: #999999; color: #999999;
margin: 0 auto;
} }
} }
//
.titleLi {
margin: 10rpx 0 10rpx 20rpx;
font-size: 26rpx;
}
.actBox {
width: 700rpx;
height: 150rpx;
background-color: #fff2f2;
margin: 10rpx auto;
border-radius: 10rpx;
}
.boxCont {
width: 700rpx;
height: 150rpx;
align-items: center;
}
.contBu {
height: 80rpx;
line-height: 80rpx;
overflow: hidden;
font-size: 30rpx;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
.cotBu-txt {
height: 70rpx;
line-height: 70rpx;
font-size: 25rpx;
color: #999999;
}
.model-content-tag2 {
font-size: 35rpx;
font-weight: 500;
color: #ff6911;
width: 200rpx;
height: 150rpx;
text-align: center;
}
.usePrice {
width: 200rpx;
height: 90rpx;
line-height: 100rpx;
// background-color: red;
}
.impose {
width: 200rpx;
height: 50rpx;
// line-height: 75rpx;
font-size: 23rpx;
// background-color: gold;
}
.model-content-title2 {
width: 330rpx;
height: 150rpx;
font-size: 26rpx;
font-weight: 500;
color: #333333;
overflow: hidden;
}
.coupon {
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
background-color: rgb(255, 68, 68);
color: white;
border-radius: 30rpx;
text-align: center;
font-size: 25rpx;
}
.coupon2 {
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
background-color: rgb(203, 192, 191);
color: white;
border-radius: 30rpx;
text-align: center;
font-size: 25rpx;
}
.nullBox {
width: 100%;
height: 300rpx;
font-size: 25rpx;
line-height: 300rpx;
text-align: center;
color: #999999;
}
</style> </style>

View File

@ -39,6 +39,8 @@
<s-groupon-block v-if="type === 'PromotionCombination'" :data="data" :styles="styles" /> <s-groupon-block v-if="type === 'PromotionCombination'" :data="data" :styles="styles" />
<!-- 营销组件秒杀 --> <!-- 营销组件秒杀 -->
<s-seckill-block v-if="type === 'PromotionSeckill'" :data="data" :styles="styles" /> <s-seckill-block v-if="type === 'PromotionSeckill'" :data="data" :styles="styles" />
<!-- 营销组件积分商城 -->
<s-point-block v-if="type === 'PromotionPoint'" :data="data" :styles="styles" />
<!-- 营销组件小程序直播暂时没有这个功能 --> <!-- 营销组件小程序直播暂时没有这个功能 -->
<s-live-block v-if="type === 'MpLive'" :data="data" :styles="styles" /> <s-live-block v-if="type === 'MpLive'" :data="data" :styles="styles" />
<!-- 营销组件优惠券 --> <!-- 营销组件优惠券 -->

View File

@ -15,25 +15,11 @@
:scroll-with-animation="false" :scroll-with-animation="false"
:enable-back-to-top="true" :enable-back-to-top="true"
> >
<view v-for="(item, index) in state.orderInfo.promo_infos" :key="index"> <view v-for="(item, index) in state.orderInfo.promotions" :key="index">
<view class="ss-flex ss-m-b-40 subtitle"> <!-- 不展示积分优惠劵会员折扣因为它们已经单独展示了 -->
<view>{{ item.goods_ids.length }}</view> <view class="ss-flex ss-m-b-40 subtitle" v-if="[1, 2, 3, 4, 5, 6].includes(item.type)">
<view v-if="item.activity_type === 'full_discount'"> <view> {{ item.description }} </view>
{{ item.discount_rule.full }}{{ item.discount_rule.discount }},已减
</view>
<view v-if="item.activity_type === 'full_gift'"></view>
<view v-if="item.activity_type === 'full_reduce'">
{{ item.discount_rule.full }}{{ item.discount_rule.discount }},已减
</view>
<view class="price-text">{{ item.promo_discount_money || '0.00' }}</view>
</view> </view>
<scroll-view class="scroll-box" scroll-x scroll-anchoring>
<view class="ss-flex">
<view v-for="i in item.goods_ids" :key="i">
<image class="content-img" :src="sheep.$url.cdn(getGoodsImg(i))" />
</view>
</view>
</scroll-view>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@ -44,7 +30,6 @@
</template> </template>
<script setup> <script setup>
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import sheep from '@/sheep';
const props = defineProps({ const props = defineProps({
promoInfo: { promoInfo: {
type: Array, type: Array,
@ -67,28 +52,22 @@
const state = reactive({ const state = reactive({
orderInfo: computed(() => props.modelValue), orderInfo: computed(() => props.modelValue),
}); });
const getGoodsImg = (e) => {
let goodsImg = '';
state.orderInfo.goods_list.forEach((i) => {
if (e == i.goods_id) {
goodsImg = i.goods.image;
}
});
return goodsImg;
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.model-box { .model-box {
height: 60vh; height: 60vh;
} }
.model-content { .model-content {
height: 54vh; height: 54vh;
} }
.modal-footer { .modal-footer {
width: 100%; width: 100%;
height: 120rpx; height: 120rpx;
background: #fff; background: #fff;
} }
.confirm-btn { .confirm-btn {
width: 710rpx; width: 710rpx;
margin-left: 20rpx; margin-left: 20rpx;
@ -97,17 +76,20 @@
border-radius: 40rpx; border-radius: 40rpx;
color: #fff; color: #fff;
} }
.content-img { .content-img {
width: 140rpx; width: 140rpx;
height: 140rpx; height: 140rpx;
margin-right: 20rpx; margin-right: 20rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
} }
.subtitle { .subtitle {
font-size: 28rpx; font-size: 28rpx;
font-weight: 500; font-weight: 500;
color: #333333; color: #333333;
} }
.price-text { .price-text {
color: #ff3000; color: #ff3000;
} }

View File

@ -144,6 +144,8 @@
import { computed, reactive, onMounted } from 'vue'; import { computed, reactive, onMounted } from 'vue';
import sheep from '@/sheep'; import sheep from '@/sheep';
import SpuApi from '@/sheep/api/product/spu'; import SpuApi from '@/sheep/api/product/spu';
import OrderApi from '@/sheep/api/trade/order';
import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
// //
const LayoutTypeEnum = { const LayoutTypeEnum = {
@ -238,6 +240,15 @@
onMounted(async () => { onMounted(async () => {
// //
state.goodsList = await getGoodsListByIds(spuIds.join(',')); state.goodsList = await getGoodsListByIds(spuIds.join(','));
//
await OrderApi.getSettlementProduct(state.goodsList.map((item) => item.id).join(',')).then(
(res) => {
if (res.code !== 0) {
return;
}
appendSettlementProduct(state.goodsList, res.data);
},
);
// //
if (layoutType === LayoutTypeEnum.TWO_COL) { if (layoutType === LayoutTypeEnum.TWO_COL) {
// //

View File

@ -11,11 +11,7 @@
<view v-if="tagStyle.show" class="tag-icon-box"> <view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image> <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
</view> </view>
<image <image class="xs-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFit" />
class="xs-img-box"
:src="sheep.$url.cdn(data.image || data.picUrl)"
mode="aspectFit"
></image>
<view <view
v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show" v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show"
class="xs-goods-content ss-flex-col ss-row-around" class="xs-goods-content ss-flex-col ss-row-around"
@ -27,13 +23,34 @@
> >
{{ data.title || data.name }} {{ data.title || data.name }}
</view> </view>
<!-- 活动信息 -->
<view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
<view class="card" v-if="discountText">{{ discountText }}</view>
<view
class="card2"
v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
:key="item"
>
{{ item }}
</view>
</view>
<view <view
v-if="goodsFields.price?.show" v-if="goodsFields.price?.show"
class="xs-goods-price font-OPPOSANS" class="xs-goods-price font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]" :style="[{ color: goodsFields.price.color }]"
> >
<text class="price-unit ss-font-24">{{ priceUnit }}</text> <!-- 活动价格 -->
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }} <text v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
{{ data.point }}积分
{{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${fen2yuan(data.pointPrice)}` }}
</text>
<template v-else>
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
<text v-else>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</text>
</template>
</view> </view>
</view> </view>
</view> </view>
@ -60,13 +77,34 @@
> >
{{ data.title || data.name }} {{ data.title || data.name }}
</view> </view>
<!-- 活动信息 -->
<view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
<view class="card" v-if="discountText">{{ discountText }}</view>
<view
class="card2"
v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
:key="item"
>
{{ item }}
</view>
</view>
<view <view
v-if="goodsFields.price?.show" v-if="goodsFields.price?.show"
class="sm-goods-price font-OPPOSANS" class="sm-goods-price font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]" :style="[{ color: goodsFields.price.color }]"
> >
<text class="price-unit ss-font-24">{{ priceUnit }}</text> <!-- 活动价格 -->
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }} <text v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
{{ data.point }}积分
{{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${fen2yuan(data.pointPrice)}` }}
</text>
<template v-else>
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
<text v-else>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</text>
</template>
</view> </view>
</view> </view>
</view> </view>
@ -74,13 +112,9 @@
<!-- md卡片竖向一行放两个图上内容下 --> <!-- md卡片竖向一行放两个图上内容下 -->
<view v-if="size === 'md'" class="md-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick"> <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"> <view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image> <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)" />
</view> </view>
<image <image class="md-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="widthFix" />
class="md-img-box"
:src="sheep.$url.cdn(data.image || data.picUrl)"
mode="widthFix"
></image>
<view <view
class="md-goods-content ss-flex-col ss-row-around ss-p-b-20 ss-p-t-20 ss-p-x-16" class="md-goods-content ss-flex-col ss-row-around ss-p-b-20 ss-p-t-20 ss-p-x-16"
:id="elId" :id="elId"
@ -110,16 +144,36 @@
</view> </view>
</view> </view>
</slot> </slot>
<!-- 活动信息 -->
<view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
<view class="card" v-if="discountText">{{ discountText }}</view>
<view
class="card2"
v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
:key="item"
>
{{ item }}
</view>
</view>
<view class="ss-flex ss-col-bottom"> <view class="ss-flex ss-col-bottom">
<view <view
v-if="goodsFields.price?.show" v-if="goodsFields.price?.show"
class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10" class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10"
:style="[{ color: goodsFields.price.color }]" :style="[{ color: goodsFields.price.color }]"
> >
<text class="price-unit ss-font-24">{{ priceUnit }}</text> <!-- 活动价格 -->
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }} <text v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
{{ data.point }}积分
{{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${fen2yuan(data.pointPrice)}` }}
</text>
<template v-else>
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
<text v-else>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</text>
</template>
</view> </view>
<view <view
v-if=" v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) && (goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
@ -163,7 +217,7 @@
class="lg-img-box" class="lg-img-box"
:src="sheep.$url.cdn(data.image || data.picUrl)" :src="sheep.$url.cdn(data.image || data.picUrl)"
mode="aspectFill" 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 class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
<view> <view>
<view <view
@ -189,21 +243,38 @@
</view> </view>
</view> </view>
</slot> </slot>
<view class="ss-flex ss-col-bottom ss-m-t-10"> <!-- 活动信息 -->
<view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
<view class="card" v-if="discountText">{{ discountText }}</view>
<view <view
v-if="goodsFields.price?.show" class="card2"
class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS" v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
:style="[{ color: goodsFields.price.color }]" :key="item"
> >
<text class="ss-font-24">{{ priceUnit }}</text> {{ item }}
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }} </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 v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
{{ data.point }}积分
{{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${fen2yuan(data.pointPrice)}` }}
</text>
<template v-else>
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
<text v-else>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</text>
</template>
</view> </view>
<view <view
v-if=" v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) && (goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
(data.original_price > 0 || data.marketPrice > 0) (data.original_price > 0 || data.marketPrice > 0)
" "
class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS" class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex"
:style="[{ color: originPriceColor }]" :style="[{ color: originPriceColor }]"
> >
<text class="price-unit ss-font-20">{{ priceUnit }}</text> <text class="price-unit ss-font-20">{{ priceUnit }}</text>
@ -217,22 +288,20 @@
</view> </view>
<slot name="cart"> <slot name="cart">
<view class="buy-box ss-flex ss-col-center ss-row-center" v-if="buttonShow"> </view> <view class="buy-box ss-flex ss-col-center ss-row-center" v-if="buttonShow"> </view>
</slot> </slot>
</view> </view>
<!-- sl卡片竖向型一行放一个图片上内容下边 --> <!-- sl卡片竖向型一行放一个图片上内容下边 -->
<view v-if="size === 'sl'" class="sl-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick"> <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"> <view v-if="tagStyle.show" class="tag-icon-box">
<image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image> <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)" />
</view> </view>
<image <image
class="sl-img-box" class="sl-img-box"
:src="sheep.$url.cdn(data.image || data.picUrl)" :src="sheep.$url.cdn(data.image || data.picUrl)"
mode="aspectFill" mode="aspectFill"
></image> />
<view class="sl-goods-content"> <view class="sl-goods-content">
<view> <view>
<view <view
@ -262,10 +331,31 @@
</view> </view>
</view> </view>
</slot> </slot>
<!-- 活动信息 -->
<view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
<view class="card" v-if="discountText">{{ discountText }}</view>
<view
class="card2"
v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
:key="item"
>
{{ item }}
</view>
</view>
<view v-if="goodsFields.price?.show" class="ss-flex ss-col-bottom font-OPPOSANS"> <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 }]"> <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) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }} <text v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
{{ data.point }}积分
{{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${fen2yuan(data.pointPrice)}` }}
</text>
<template v-else>
<text class="price-unit ss-font-24">{{ priceUnit }}</text>
<text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
<text v-else>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</text>
</template>
</view> </view>
<view <view
v-if=" v-if="
@ -321,14 +411,17 @@
* @event {Function()} click - 点击卡片 * @event {Function()} click - 点击卡片
* *
*/ */
import { computed, reactive, getCurrentInstance, onMounted, nextTick } from 'vue'; import { computed, getCurrentInstance, nextTick, onMounted } from 'vue';
import sheep from '@/sheep'; import sheep from '@/sheep';
import { fen2yuan, formatSales } from '@/sheep/hooks/useGoods'; import {
import { formatStock } from '@/sheep/hooks/useGoods'; fen2yuan,
formatExchange,
formatSales,
formatStock,
getRewardActivityRuleItemDescriptions,
} from '@/sheep/hooks/useGoods';
import { isArray } from 'lodash-es'; import { isArray } from 'lodash-es';
import { PromotionActivityTypeEnum } from '@/sheep/util/const';
//
const state = reactive({});
// //
const props = defineProps({ const props = defineProps({
@ -337,17 +430,29 @@
default() { default() {
return { return {
// //
price: { show: true }, price: {
show: true,
},
// //
stock: { show: true }, stock: {
show: true,
},
// //
name: { show: true }, name: {
show: true,
},
// //
introduction: { show: true }, introduction: {
show: true,
},
// //
marketPrice: { show: true }, marketPrice: {
show: true,
},
// //
salesCount: { show: true }, salesCount: {
show: true,
},
}; };
}, },
}, },
@ -417,6 +522,17 @@
}, },
}); });
//
const discountText = computed(() => {
const promotionType = props.data.promotionType;
if (promotionType === 4) {
return '限时优惠';
} else if (promotionType === 6) {
return '会员价';
}
return undefined;
});
// //
const elStyles = computed(() => { const elStyles = computed(() => {
return { return {
@ -432,10 +548,18 @@
const salesAndStock = computed(() => { const salesAndStock = computed(() => {
let text = []; let text = [];
if (props.goodsFields.salesCount?.show) { if (props.goodsFields.salesCount?.show) {
text.push(formatSales(props.data.sales_show_type, props.data.salesCount)); if (props.data.activityType && props.data.activityType === PromotionActivityTypeEnum.POINT.type) {
text.push(formatExchange(props.data.sales_show_type, (props.data.pointTotalStock || 0) - (props.data.pointStock || 0)));
}else {
text.push(formatSales(props.data.sales_show_type, props.data.salesCount));
}
} }
if (props.goodsFields.stock?.show) { if (props.goodsFields.stock?.show) {
text.push(formatStock(props.data.stock_show_type, props.data.stock)); if (props.data.activityType && props.data.activityType === PromotionActivityTypeEnum.POINT.type) {
text.push(formatStock(props.data.stock_show_type, props.data.pointTotalStock));
}else {
text.push(formatStock(props.data.stock_show_type, props.data.stock));
}
} }
return text.join(' | '); return text.join(' | ');
}); });
@ -454,7 +578,10 @@
function getGoodsPriceCardWH() { function getGoodsPriceCardWH() {
if (props.size === 'md') { if (props.size === 'md') {
const view = uni.createSelectorQuery().in(proxy); const view = uni.createSelectorQuery().in(proxy);
view.select(`#${elId}`).fields({ size: true, scrollOffset: true }); view.select(`#${elId}`).fields({
size: true,
scrollOffset: true,
});
view.exec((data) => { view.exec((data) => {
let totalHeight = 0; let totalHeight = 0;
const goodsPriceCard = data[0]; const goodsPriceCard = data[0];
@ -763,4 +890,33 @@
color: #ffffff; color: #ffffff;
} }
} }
.card {
width: fit-content;
height: fit-content;
padding: 2rpx 10rpx;
background-color: red;
color: #ffffff;
font-size: 24rpx;
margin-top: 5rpx;
}
.card2 {
width: fit-content;
height: fit-content;
padding: 2rpx 10rpx;
background-color: rgb(255, 242, 241);
color: #ff2621;
font-size: 24rpx;
margin: 5rpx 0 5rpx 5rpx;
}
.iconBox {
width: 100%;
height: fit-content;
margin-top: 10rpx;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
}
</style> </style>

View File

@ -28,6 +28,14 @@
> >
{{ fen2yuan(price) }} {{ fen2yuan(price) }}
</view> </view>
<view v-if="point && Number(price) > 0">+</view>
<view class="price-text ss-flex ss-col-center" v-if="point">
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="point-img"
></image>
<view>{{ point }}</view>
</view>
<view v-if="num" class="total-text ss-flex ss-col-center">x {{ num }}</view> <view v-if="num" class="total-text ss-flex ss-col-center">x {{ num }}</view>
<slot name="priceSuffix"></slot> <slot name="priceSuffix"></slot>
</view> </view>
@ -88,7 +96,7 @@
type: [String, Number], type: [String, Number],
default: 0, default: 0,
}, },
score: { point: {
type: [String, Number], type: [String, Number],
default: '', default: '',
}, },
@ -113,7 +121,7 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.score-img { .point-img {
width: 36rpx; width: 36rpx;
height: 36rpx; height: 36rpx;
margin: 0 4rpx; margin: 0 4rpx;

View File

@ -0,0 +1,328 @@
<!-- 装修商品组件积分商城商品卡片 -->
<template>
<!-- 商品卡片 -->
<view>
<!-- 布局1. 单列大图上图下内容-->
<view
v-if="layoutType === LayoutTypeEnum.ONE_COL_BIG_IMG && state.spuList.length"
class="goods-sl-box"
>
<view
class="goods-box"
v-for="item in state.spuList"
:key="item.id"
:style="[{ marginBottom: data.space * 2 + 'rpx' }]"
>
<s-goods-column
class=""
size="sl"
:goodsFields="data.fields"
:tagStyle="data.badge"
:data="item"
:titleColor="data.fields.name?.color"
:subTitleColor="data.fields.introduction.color"
:topRadius="data.borderRadiusTop"
:bottomRadius="data.borderRadiusBottom"
@click="sheep.$router.go('/pages/goods/point', { id: item.activityId })"
>
<!-- 购买按钮 -->
<template v-slot:cart>
<button class="ss-reset-button cart-btn" :style="[buyStyle]">
{{ btnBuy.type === 'text' ? btnBuy.text : '' }}
</button>
</template>
</s-goods-column>
</view>
</view>
<!-- 布局2. 单列小图左图右内容 -->
<view
v-if="layoutType === LayoutTypeEnum.ONE_COL_SMALL_IMG && state.spuList.length"
class="goods-lg-box"
>
<view
class="goods-box"
:style="[{ marginBottom: data.space + 'px' }]"
v-for="item in state.spuList"
:key="item.id"
>
<s-goods-column
class="goods-card"
size="lg"
:goodsFields="data.fields"
:data="item"
:tagStyle="data.badge"
:titleColor="data.fields.name?.color"
:subTitleColor="data.fields.introduction.color"
:topRadius="data.borderRadiusTop"
:bottomRadius="data.borderRadiusBottom"
@tap="sheep.$router.go('/pages/goods/point', { id: item.activityId })"
>
<!-- 购买按钮 -->
<template v-slot:cart>
<button class="ss-reset-button cart-btn" :style="[buyStyle]">
{{ btnBuy.type === 'text' ? btnBuy.text : '' }}
</button>
</template>
</s-goods-column>
</view>
</view>
<!-- 布局3. 双列每一列上图下内容-->
<view
v-if="layoutType === LayoutTypeEnum.TWO_COL && state.spuList.length"
class="goods-md-wrap ss-flex ss-flex-wrap ss-col-top"
>
<view class="goods-list-box">
<view
class="left-list"
:style="[{ paddingRight: data.space + 'rpx', marginBottom: data.space + 'px' }]"
v-for="item in state.leftSpuList"
:key="item.id"
>
<s-goods-column
class="goods-md-box"
size="md"
:goodsFields="data.fields"
:tagStyle="data.badge"
:data="item"
:titleColor="data.fields.name?.color"
:subTitleColor="data.fields.introduction.color"
:topRadius="data.borderRadiusTop"
:bottomRadius="data.borderRadiusBottom"
:titleWidth="330 - marginLeft - marginRight"
@click="sheep.$router.go('/pages/goods/seckill', { id: item.activityId })"
@getHeight="calculateGoodsColumn($event, 'left')"
>
<!-- 购买按钮 -->
<template v-slot:cart>
<button class="ss-reset-button cart-btn" :style="[buyStyle]">
{{ btnBuy.type === 'text' ? btnBuy.text : '' }}
</button>
</template>
</s-goods-column>
</view>
</view>
<view class="goods-list-box">
<view
class="right-list"
:style="[{ paddingLeft: data.space + 'rpx', marginBottom: data.space + 'px' }]"
v-for="item in state.rightSpuList"
:key="item.id"
>
<s-goods-column
class="goods-md-box"
size="md"
:goodsFields="data.fields"
:tagStyle="data.badge"
:data="item"
:titleColor="data.fields.name?.color"
:subTitleColor="data.fields.introduction.color"
:topRadius="data.borderRadiusTop"
:bottomRadius="data.borderRadiusBottom"
:titleWidth="330 - marginLeft - marginRight"
@click="sheep.$router.go('/pages/goods/seckill', { id: item.activityId })"
@getHeight="calculateGoodsColumn($event, 'right')"
>
<!-- 购买按钮 -->
<template v-slot:cart>
<button class="ss-reset-button cart-btn" :style="[buyStyle]">
{{ btnBuy.type === 'text' ? btnBuy.text : '' }}
</button>
</template>
</s-goods-column>
</view>
</view>
</view>
</view>
</template>
<script setup>
/**
* 商品卡片
*/
import { computed, onMounted, reactive, ref } from 'vue';
import sheep from '@/sheep';
import SpuApi from '@/sheep/api/product/spu';
import PointApi from '@/sheep/api/promotion/point';
import { PromotionActivityTypeEnum } from '@/sheep/util/const';
//
const LayoutTypeEnum = {
//
ONE_COL_BIG_IMG: 'oneColBigImg',
//
TWO_COL: 'twoCol',
//
ONE_COL_SMALL_IMG: 'oneColSmallImg',
};
const state = reactive({
spuList: [],
leftSpuList: [],
rightSpuList: [],
});
const props = defineProps({
data: {
type: Object,
default() {},
},
styles: {
type: Object,
default() {},
},
});
const { layoutType, btnBuy, activityIds } = props.data || {};
const { marginLeft, marginRight } = props.styles || {};
//
const buyStyle = computed(() => {
if (btnBuy.type === 'text') {
// 线
return {
background: `linear-gradient(to right, ${btnBuy.bgBeginColor}, ${btnBuy.bgEndColor})`,
};
}
if (btnBuy.type === 'img') {
//
return {
width: '54rpx',
height: '54rpx',
background: `url(${sheep.$url.cdn(btnBuy.imgUrl)}) no-repeat`,
backgroundSize: '100% 100%',
};
}
});
//region
//
let count = 0;
//
let leftHeight = 0;
//
let rightHeight = 0;
/**
* 计算商品在左列还是右列
* @param height 商品的高度
* @param where 添加到哪一列
*/
function calculateGoodsColumn(height = 0, where = 'left') {
//
if (!state.spuList[count]) return;
//
if (where === 'left') leftHeight += height;
if (where === 'right') rightHeight += height;
//
if (leftHeight <= rightHeight) {
state.leftSpuList.push(state.spuList[count]);
} else {
state.rightSpuList.push(state.spuList[count]);
}
//
count++;
}
//endregion
/**
* 根据商品编号列表获取商品列表
* @param ids 商品编号列表
* @return {Promise<undefined>} 商品列表
*/
async function getPointActivityDetailList(ids) {
const { data } = await PointApi.getPointActivityListByIds(ids);
return data;
}
/**
* 根据商品编号获取商品详情
* @param ids 商品编号列表
* @return {Promise<undefined>} 商品列表
*/
async function getSpuDetail(ids) {
const { data: spu } = await SpuApi.getSpuDetail(ids);
return spu;
}
//
onMounted(async () => {
//
const activityList = await getPointActivityDetailList(activityIds.join(','));
// SPUspuList
for (const activity of activityList) {
state.spuList.push(await getSpuDetail(activity.spuId));
}
//
activityList.forEach((activity) => {
// spu
const spu = state.spuList.find((spu) => activity.spuId === spu.id);
if (spu) {
spu.pointStock = activity.stock
spu.pointTotalStock = activity.totalStock
spu.point = activity.point
spu.pointPrice = activity.price
// ID
spu.activityId = activity.id;
//
spu.activityType = PromotionActivityTypeEnum.POINT.type;
}
});
//
if (layoutType === LayoutTypeEnum.TWO_COL) {
//
calculateGoodsColumn();
}
});
</script>
<style lang="scss" scoped>
.goods-md-wrap {
width: 100%;
}
.goods-list-box {
width: 50%;
box-sizing: border-box;
.left-list {
&:nth-last-child(1) {
margin-bottom: 0 !important;
}
}
.right-list {
&:nth-last-child(1) {
margin-bottom: 0 !important;
}
}
}
.goods-box {
&:nth-last-of-type(1) {
margin-bottom: 0 !important;
}
}
.goods-md-box,
.goods-sl-box,
.goods-lg-box {
position: relative;
.cart-btn {
position: absolute;
bottom: 18rpx;
right: 20rpx;
z-index: 11;
height: 50rpx;
line-height: 50rpx;
padding: 0 20rpx;
border-radius: 25rpx;
font-size: 24rpx;
color: #fff;
}
}
</style>

View File

@ -0,0 +1,458 @@
<template>
<view>
<!-- md卡片竖向一行放两个图上内容下 -->
<view v-if="size === 'md'" class="md-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
<image
class="md-img-box"
:src="sheep.$url.cdn(data.image)"
mode="widthFix"
@load="calculatePanelHeight"
></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"
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 }]"
>
{{ data.subtitle }}
</view>
<view class="ss-col-bottom">
<view
v-if="goodsFields.score_price?.show"
class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10 ss-flex"
:style="[{ color: goodsFields.score_price.color }]"
>
<view>{{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}</view>
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="score-img"
></image>
{{ data.score }}
</view>
<view
v-if="goodsFields.price?.show && data.original_price > 0"
class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex"
:style="[{ color: goodsFields.price.color }]"
>
<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">{{ 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=""></image>
</view>
</slot>
</view>
<!-- lg卡片横向型一行放一个图片左内容右边 -->
<view
v-if="size === 'lg'"
class="lg-goods-card ss-flex ss-col-stretch"
:style="[elStyles]"
@tap="onClick"
>
<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 class="ss-m-r-20">
<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 }]"
>
{{ data.subtitle }}
</view>
</view>
<view>
<view class="ss-m-t-10">
<view
v-if="goodsFields.score_price?.show"
class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
:style="[{ color: goodsFields.score_price.color }]"
>
<view>{{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}</view>
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="score-img"
></image>
{{ data.score }}
</view>
<view
v-if="goodsFields.price?.show && data.original_price > 0"
class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]"
>
<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">{{ salesAndStock }}</view>
</view>
</view>
</view>
<slot name="cart"
><view class="buy-box ss-flex ss-col-center ss-row-center">去兑换</view></slot
>
</view>
<!-- sl卡片竖向型一行放一个图片上内容下边 -->
<view v-if="size === 'sl'" class="sl-goods-card ss-flex-col" @tap="onClick">
<image class="sl-img-box" :src="sheep.$url.cdn(data.image)" mode="aspectFill"></image>
<view class="sl-goods-content ss-flex-col ss-row-between ss-p-b-20 ss-p-t-20">
<view class="ss-m-b-20">
<view class="sl-goods-title ss-line-1 ss-p-l-16 ss-p-r-16">
{{ data.title }}
</view>
<view v-if="data.subtitle" class="sl-goods-subtitle ss-p-l-16 ss-p-r-16 ss-m-t-16">
{{ 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 ss-p-l-16 ss-p-r-16"
>
<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 ss-p-l-16 ss-p-r-16 font-OPPOSANS">
<view class="sl-goods-price ss-m-r-12 ss-flex">
<view>{{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}</view>
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="score-img"
></image>
<view>{{ data.score ? data.score : '' }}</view>
</view>
<view
v-if="data.original_price > 0"
class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex"
>
<text class="price-unit ss-font-20"></text>
<view class="ss-m-l-8">{{ data.original_price }}</view>
</view>
</view>
<view class="ss-p-l-16 ss-p-r-16 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>
import { computed, getCurrentInstance } from 'vue';
import sheep from '@/sheep';
import { formatSales } from '@/sheep/hooks/useGoods';
import { formatStock } from '@/sheep/hooks/useGoods';
/**
* 订单卡片
*
* @property {String} img - 图片
* @property {String} title - 标题
* @property {Number} titleWidth = 0 - 标题宽度默认0单位rpx
* @property {String} skuText - 规格
* @property {String | Number} score - 积分
* @property {String | Number} price - 价格
* @property {String | Number} originalPrice - 单购价
* @property {String} priceColor - 价格颜色
* @property {Number | String} num - 数量
*
*/
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',
},
priceUnit: {
type: String,
default: '¥',
},
subTitleColor: {
type: String,
default: '#999999',
},
});
//
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 emits = defineEmits(['click', 'getHeight']);
const onClick = () => {
emits('click');
};
//
const salesAndStock = computed(() => {
let text = [];
text.push(formatSales(props.data.sales_show_type, props.data.sales));
text.push(formatStock(props.data.stock_show_type, props.data.stock));
return text.join(' | ');
});
//
const { proxy } = getCurrentInstance();
const elId = `sheep_${Math.ceil(Math.random() * 10e5).toString(36)}`;
function calculatePanelHeight(e) {
if (props.size === 'md') {
const view = uni.createSelectorQuery().in(proxy);
view.select(`#${elId}`).fields({ size: true, scrollOffset: true });
view.exec((data) => {
const goodsPriceCard = data[0];
const card = {
width: goodsPriceCard.width,
height: (goodsPriceCard.width / e.detail.width) * e.detail.height + goodsPriceCard.height,
};
emits('getHeight', card.height);
});
}
}
</script>
<style lang="scss" scoped>
.price-unit {
margin-right: -4px;
}
.sales-text {
display: table;
font-size: 24rpx;
transform: scale(0.8);
margin-left: -16rpx;
color: #c4c4c4;
}
// 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, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 25rpx;
font-size: 24rpx;
color: #ffffff;
}
.tag-box {
width: 100%;
}
}
.sl-goods-card {
overflow: hidden;
position: relative;
z-index: 1;
width: 100%;
background-color: $white;
.sl-img-box {
width: 100%;
height: 360rpx;
}
.sl-goods-title {
font-size: 26rpx;
color: #333;
width: 100%;
box-sizing: border-box;
}
.sl-goods-subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999999;
line-height: 30rpx;
width: 100%;
box-sizing: border-box;
}
.sl-goods-price {
font-size: 30rpx;
color: $red;
}
.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;
}
}
.goods-origin-price {
font-size: 20rpx;
color: #c4c4c4;
text-decoration: line-through;
}
.score-img {
width: 36rpx;
height: 36rpx;
margin: 0 4rpx;
}
</style>

View File

@ -19,8 +19,11 @@
<view class="goods-title ss-line-2">{{ state.goodsInfo.name }}</view> <view class="goods-title ss-line-2">{{ state.goodsInfo.name }}</view>
<view class="header-right-bottom ss-flex ss-col-center ss-row-between"> <view class="header-right-bottom ss-flex ss-col-center ss-row-between">
<!-- 价格 --> <!-- 价格 -->
<view class="price-text"> <view v-if="state.goodsInfo.activity_type === PromotionActivityTypeEnum.POINT.type" class="price-text">
{{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }} {{ getShowPriceText }}
</view>
<view v-else class="price-text">
{{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
</view> </view>
<!-- 秒杀价格标签 --> <!-- 秒杀价格标签 -->
<view class="tig ss-flex ss-col-center"> <view class="tig ss-flex ss-col-center">
@ -92,12 +95,15 @@
import { computed, reactive, watch } from 'vue'; import { computed, reactive, watch } from 'vue';
import sheep from '@/sheep'; import sheep from '@/sheep';
import { convertProductPropertyList, fen2yuan } from '@/sheep/hooks/useGoods'; import { convertProductPropertyList, fen2yuan } from '@/sheep/hooks/useGoods';
import { min } from 'lodash-es'; import { isEmpty, min } from 'lodash-es';
import { PromotionActivityTypeEnum } from '@/sheep/util/const';
const emits = defineEmits(['change', 'addCart', 'buy', 'close']); const emits = defineEmits(['change', 'addCart', 'buy', 'close']);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
default() {}, default() {
},
}, },
show: { show: {
type: Boolean, type: Boolean,
@ -114,7 +120,14 @@
selectedSku: {}, selectedSku: {},
currentPropertyArray: [], currentPropertyArray: [],
}); });
const getShowPriceText = computed(() => {
let priceText = `${fen2yuan(state.goodsInfo.price)}`;
if (!isEmpty(state.selectedSku)) {
const sku = state.selectedSku;
priceText = `${sku.point}积分${!sku.pointPrice ? '' : `+¥${fen2yuan(sku.pointPrice)}`}`;
}
return priceText;
});
const propertyList = convertProductPropertyList(state.goodsInfo.skus); const propertyList = convertProductPropertyList(state.goodsInfo.skus);
// SKU // SKU
const skuList = computed(() => { const skuList = computed(() => {
@ -344,11 +357,6 @@
font-weight: 500; font-weight: 500;
color: $red; color: $red;
font-family: OPPOSANS; font-family: OPPOSANS;
&::before {
content: '¥';
font-size: 24rpx;
}
} }
.stock-text { .stock-text {

View File

@ -1,106 +1,139 @@
<template> <template>
<!-- 规格弹窗 --> <!-- 规格弹窗 -->
<su-popup :show="show" round="10" @close="emits('close')"> <su-popup :show="show" round="10" @close="emits('close')">
<!-- SKU 信息 --> <!-- SKU 信息 -->
<view class="ss-modal-box bg-white ss-flex-col"> <view class="ss-modal-box bg-white ss-flex-col">
<view class="modal-header ss-flex ss-col-center"> <view class="modal-header ss-flex ss-col-center">
<view class="header-left ss-m-r-30"> <view class="header-left ss-m-r-30">
<image class="sku-image" :src="state.selectedSku.picUrl || goodsInfo.picUrl" mode="aspectFill" /> <image
</view> class="sku-image"
<view class="header-right ss-flex-col ss-row-between ss-flex-1"> :src="state.selectedSku.picUrl || goodsInfo.picUrl"
<view class="goods-title ss-line-2">{{ goodsInfo.name }}</view> mode="aspectFill"
<view class="header-right-bottom ss-flex ss-col-center ss-row-between"> />
<view class="ss-flex"> </view>
<view class="price-text"> <view class="header-right ss-flex-col ss-row-between ss-flex-1">
{{ fen2yuan( state.selectedSku.price || goodsInfo.price) }} <view class="goods-title ss-line-2">{{ goodsInfo.name }}</view>
</view> <view class="header-right-bottom ss-flex ss-col-center ss-row-between">
</view> <view class="ss-flex">
<view class="stock-text ss-m-l-20"> <view class="price-text">
{{ formatStock('exact', state.selectedSku.stock || goodsInfo.stock) }} {{
</view> fen2yuan(
</view> state.selectedSku.promotionPrice || state.selectedSku.price || goodsInfo.price,
</view> )
</view> }}
<text v-if="state.selectedSku.promotionType > 0">
<text class="iconBox" v-if="state.selectedSku.promotionType === 4">
限时优惠
</text>
<text class="iconBox" v-else-if="state.selectedSku.promotionType === 6">
会员价
</text>
<text class="origin-price-text">
{{ fen2yuan(state.selectedSku.price) }}
</text>
</text>
</view>
</view>
<view class="stock-text ss-m-l-20">
{{ formatStock('exact', state.selectedSku.stock || goodsInfo.stock) }}
</view>
</view>
</view>
</view>
<!-- 属性选择 --> <!-- 属性选择 -->
<view class="modal-content ss-flex-1"> <view class="modal-content ss-flex-1">
<scroll-view scroll-y="true" class="modal-content-scroll" @touchmove.stop> <scroll-view scroll-y="true" class="modal-content-scroll" @touchmove.stop>
<view class="sku-item ss-m-b-20" v-for="property in propertyList" :key="property.id"> <view class="sku-item ss-m-b-20" v-for="property in propertyList" :key="property.id">
<view class="label-text ss-m-b-20">{{ property.name }}</view> <view class="label-text ss-m-b-20">{{ property.name }}</view>
<view class="ss-flex ss-col-center ss-flex-wrap"> <view class="ss-flex ss-col-center ss-flex-wrap">
<button class="ss-reset-button spec-btn" v-for="value in property.values" :class="[ <button
class="ss-reset-button spec-btn"
v-for="value in property.values"
:class="[
{ {
'ui-BG-Main-Gradient': state.currentPropertyArray[property.id] === value.id, 'ui-BG-Main-Gradient': state.currentPropertyArray[property.id] === value.id,
}, },
{ {
'disabled-btn': value.disabled === true, 'disabled-btn': value.disabled === true,
}, },
]" :key="value.id" :disabled="value.disabled === true" @tap="onSelectSku(property.id, value.id)"> ]"
{{ value.name }} :key="value.id"
</button> :disabled="value.disabled === true"
</view> @tap="onSelectSku(property.id, value.id)"
</view> >
<view class="buy-num-box ss-flex ss-col-center ss-row-between ss-m-b-40"> {{ value.name }}
<view class="label-text">购买数量</view> </button>
<su-number-box :min="1" :max="state.selectedSku.stock" :step="1" </view>
v-model="state.selectedSku.goods_num" @change="onNumberChange($event)" /> </view>
</view> <view class="buy-num-box ss-flex ss-col-center ss-row-between ss-m-b-40">
</scroll-view> <view class="label-text">购买数量</view>
</view> <su-number-box
:min="1"
:max="state.selectedSku.stock"
:step="1"
v-model="state.selectedSku.goods_num"
@change="onNumberChange($event)"
/>
</view>
</scroll-view>
</view>
<!-- 操作区 --> <!-- 操作区 -->
<view class="modal-footer border-top"> <view class="modal-footer border-top">
<view class="buy-box ss-flex ss-col-center ss-flex ss-col-center ss-row-center"> <view class="buy-box ss-flex ss-col-center ss-flex ss-col-center ss-row-center">
<button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart"></button> <button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart"
<button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="onBuy"></button> >加入购物车</button
</view> >
</view> <button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="onBuy"></button>
</view> </view>
</su-popup> </view>
</view>
</su-popup>
</template> </template>
<script setup> <script setup>
import { computed, reactive, watch } from 'vue'; import { computed, reactive, watch } from 'vue';
import sheep from '@/sheep'; import sheep from '@/sheep';
import { formatStock, convertProductPropertyList, fen2yuan } from '@/sheep/hooks/useGoods'; import { formatStock, convertProductPropertyList, fen2yuan } from '@/sheep/hooks/useGoods';
const emits = defineEmits(['change', 'addCart', 'buy', 'close']); const emits = defineEmits(['change', 'addCart', 'buy', 'close']);
const props = defineProps({ const props = defineProps({
goodsInfo: { goodsInfo: {
type: Object, type: Object,
default () {}, default() {},
}, },
show: { show: {
type: Boolean, type: Boolean,
default: false, default: false,
} },
}); });
const state = reactive({ const state = reactive({
selectedSku: {}, // SKU selectedSku: {}, // SKU
currentPropertyArray: [], // Mapkey property value value currentPropertyArray: [], // Mapkey property value value
}); });
const propertyList = convertProductPropertyList(props.goodsInfo.skus); const propertyList = convertProductPropertyList(props.goodsInfo.skus);
// SKU
// SKU const skuList = computed(() => {
const skuList = computed(() => { let skuPrices = props.goodsInfo.skus;
let skuPrices = props.goodsInfo.skus;
for (let price of skuPrices) { for (let price of skuPrices) {
price.value_id_array = price.properties.map((item) => item.valueId) price.value_id_array = price.properties.map((item) => item.valueId);
} }
return skuPrices; return skuPrices;
}); });
watch( watch(
() => state.selectedSku, () => state.selectedSku,
(newVal) => { (newVal) => {
emits('change', newVal); emits('change', newVal);
}, { },
immediate: true, // {
deep: true, // immediate: true, //
}, deep: true, //
); },
);
// //
function onNumberChange(e) { function onNumberChange(e) {
@ -110,21 +143,21 @@
} }
// //
function onAddCart() { function onAddCart() {
if (state.selectedSku.id <= 0) { if (state.selectedSku.id <= 0) {
sheep.$helper.toast('请选择规格'); sheep.$helper.toast('请选择规格');
return; return;
} }
if (state.selectedSku.stock <= 0) { if (state.selectedSku.stock <= 0) {
sheep.$helper.toast('库存不足'); sheep.$helper.toast('库存不足');
return; return;
} }
emits('addCart', state.selectedSku); emits('addCart', state.selectedSku);
} }
// //
function onBuy() { function onBuy() {
if (state.selectedSku.id <= 0) { if (state.selectedSku.id <= 0) {
sheep.$helper.toast('请选择规格'); sheep.$helper.toast('请选择规格');
return; return;
@ -134,273 +167,298 @@
return; return;
} }
emits('buy', state.selectedSku); emits('buy', state.selectedSku);
} }
// property // property
function changeDisabled(isChecked = false, propertyId = 0, valueId = 0) { function changeDisabled(isChecked = false, propertyId = 0, valueId = 0) {
let newSkus = []; // sku let newSkus = []; // sku
if (isChecked) { if (isChecked) {
// property // property
// property SKU // property SKU
for (let price of skuList.value) { for (let price of skuList.value) {
if (price.stock <= 0) { if (price.stock <= 0) {
continue; continue;
} }
if (price.value_id_array.indexOf(valueId) >= 0) { if (price.value_id_array.indexOf(valueId) >= 0) {
newSkus.push(price); newSkus.push(price);
} }
} }
} else { } else {
// property // property
// property SKU // property SKU
newSkus = getCanUseSkuList(); newSkus = getCanUseSkuList();
} }
// SKU value id // SKU value id
let noChooseValueIds = []; let noChooseValueIds = [];
for (let price of newSkus) { for (let price of newSkus) {
noChooseValueIds = noChooseValueIds.concat(price.value_id_array); noChooseValueIds = noChooseValueIds.concat(price.value_id_array);
} }
noChooseValueIds = Array.from(new Set(noChooseValueIds)); // noChooseValueIds = Array.from(new Set(noChooseValueIds)); //
if (isChecked) { if (isChecked) {
// value id // value id
let index = noChooseValueIds.indexOf(valueId); let index = noChooseValueIds.indexOf(valueId);
noChooseValueIds.splice(index, 1); noChooseValueIds.splice(index, 1);
} else { } else {
// value id // value id
state.currentPropertyArray.forEach((currentPropertyId) => { state.currentPropertyArray.forEach((currentPropertyId) => {
if (currentPropertyId.toString() !== '') { if (currentPropertyId.toString() !== '') {
return; return;
} }
// currentPropertyId // currentPropertyId
let index = noChooseValueIds.indexOf(currentPropertyId); let index = noChooseValueIds.indexOf(currentPropertyId);
if (index >= 0) { if (index >= 0) {
// currentPropertyId noChooseValueIds // currentPropertyId noChooseValueIds
noChooseValueIds.splice(index, 1); noChooseValueIds.splice(index, 1);
} }
}); });
} }
// property // property
let choosePropertyIds = []; let choosePropertyIds = [];
if (!isChecked) { if (!isChecked) {
// property // property
state.currentPropertyArray.forEach((currentPropertyId, currentValueId) => { state.currentPropertyArray.forEach((currentPropertyId, currentValueId) => {
if (currentPropertyId !== '') { if (currentPropertyId !== '') {
// currentPropertyId // currentPropertyId
choosePropertyIds.push(currentValueId); choosePropertyIds.push(currentValueId);
} }
}); });
} else { } else {
// property // property
choosePropertyIds = [propertyId]; choosePropertyIds = [propertyId];
} }
for (let propertyIndex in propertyList) { for (let propertyIndex in propertyList) {
// property property // property property
if (choosePropertyIds.indexOf(propertyList[propertyIndex]['id']) >= 0) { if (choosePropertyIds.indexOf(propertyList[propertyIndex]['id']) >= 0) {
continue; continue;
} }
// property id SKU // property id SKU
for (let valueIndex in propertyList[propertyIndex]['values']) { for (let valueIndex in propertyList[propertyIndex]['values']) {
propertyList[propertyIndex]['values'][valueIndex]['disabled'] = propertyList[propertyIndex]['values'][valueIndex]['disabled'] =
noChooseValueIds.indexOf(propertyList[propertyIndex]['values'][valueIndex]['id']) < 0; // true or false noChooseValueIds.indexOf(propertyList[propertyIndex]['values'][valueIndex]['id']) < 0; // true or false
} }
} }
} }
// SKU // SKU
function getCanUseSkuList() { function getCanUseSkuList() {
let newSkus = []; let newSkus = [];
for (let sku of skuList.value) { for (let sku of skuList.value) {
if (sku.stock <= 0) { if (sku.stock <= 0) {
continue; continue;
} }
let isOk = true; let isOk = true;
state.currentPropertyArray.forEach((propertyId) => { state.currentPropertyArray.forEach((propertyId) => {
// propertyId sku // propertyId sku
if (propertyId.toString() !== '' && sku.value_id_array.indexOf(propertyId) < 0) { if (propertyId.toString() !== '' && sku.value_id_array.indexOf(propertyId) < 0) {
isOk = false; isOk = false;
} }
}); });
if (isOk) { if (isOk) {
newSkus.push(sku); newSkus.push(sku);
} }
} }
return newSkus; return newSkus;
} }
// //
function onSelectSku(propertyId, valueId) { function onSelectSku(propertyId, valueId) {
// //
let isChecked = true; // or let isChecked = true; // or
if (state.currentPropertyArray[propertyId] !== undefined && state.currentPropertyArray[propertyId] === valueId) { if (
// '' state.currentPropertyArray[propertyId] !== undefined &&
isChecked = false; state.currentPropertyArray[propertyId] === valueId
state.currentPropertyArray.splice(propertyId, 1, ''); ) {
} else { // ''
// isChecked = false;
state.currentPropertyArray[propertyId] = valueId; state.currentPropertyArray.splice(propertyId, 1, '');
} } else {
//
state.currentPropertyArray[propertyId] = valueId;
}
// property // property
let choosePropertyId = []; let choosePropertyId = [];
state.currentPropertyArray.forEach((currentPropertyId) => { state.currentPropertyArray.forEach((currentPropertyId) => {
if (currentPropertyId !== '') { if (currentPropertyId !== '') {
// currentPropertyId // currentPropertyId
choosePropertyId.push(currentPropertyId); choosePropertyId.push(currentPropertyId);
} }
}); });
// property SKU // property SKU
let newSkuList = getCanUseSkuList(); let newSkuList = getCanUseSkuList();
// property // property
if (choosePropertyId.length === propertyList.length && newSkuList.length) { if (choosePropertyId.length === propertyList.length && newSkuList.length) {
newSkuList[0].goods_num = state.selectedSku.goods_num || 1; newSkuList[0].goods_num = state.selectedSku.goods_num || 1;
state.selectedSku = newSkuList[0]; state.selectedSku = newSkuList[0];
} else { } else {
state.selectedSku = {}; state.selectedSku = {};
} }
// property // property
changeDisabled(isChecked, propertyId, valueId); changeDisabled(isChecked, propertyId, valueId);
} }
changeDisabled(false); changeDisabled(false);
// TODO 12 // TODO 12
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// //
.buy-box { .buy-box {
padding: 10rpx 0; padding: 10rpx 0;
.add-btn { .add-btn {
width: 356rpx; width: 356rpx;
height: 80rpx; height: 80rpx;
border-radius: 40rpx 0 0 40rpx; border-radius: 40rpx 0 0 40rpx;
background-color: var(--ui-BG-Main-light); background-color: var(--ui-BG-Main-light);
color: var(--ui-BG-Main); color: var(--ui-BG-Main);
} }
.buy-btn { .buy-btn {
width: 356rpx; width: 356rpx;
height: 80rpx; height: 80rpx;
border-radius: 0 40rpx 40rpx 0; border-radius: 0 40rpx 40rpx 0;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: #fff; color: #fff;
} }
.score-btn { .score-btn {
width: 100%; width: 100%;
margin: 0 20rpx; margin: 0 20rpx;
height: 80rpx; height: 80rpx;
border-radius: 40rpx; border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: #fff; color: #fff;
} }
} }
.ss-modal-box { .ss-modal-box {
border-radius: 30rpx 30rpx 0 0; border-radius: 30rpx 30rpx 0 0;
max-height: 1000rpx; max-height: 1000rpx;
.modal-header { .modal-header {
position: relative; position: relative;
padding: 80rpx 20rpx 40rpx; padding: 80rpx 20rpx 40rpx;
.sku-image { .sku-image {
width: 160rpx; width: 160rpx;
height: 160rpx; height: 160rpx;
border-radius: 10rpx; border-radius: 10rpx;
} }
.header-right { .header-right {
height: 160rpx; height: 160rpx;
} }
.close-icon { .close-icon {
position: absolute; position: absolute;
top: 10rpx; top: 10rpx;
right: 20rpx; right: 20rpx;
font-size: 46rpx; font-size: 46rpx;
opacity: 0.2; opacity: 0.2;
} }
.goods-title { .goods-title {
font-size: 28rpx; font-size: 28rpx;
font-weight: 500; font-weight: 500;
line-height: 42rpx; line-height: 42rpx;
} }
.score-img { .score-img {
width: 36rpx; width: 36rpx;
height: 36rpx; height: 36rpx;
margin: 0 4rpx; margin: 0 4rpx;
} }
.score-text { .score-text {
font-size: 30rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;
color: $red; color: $red;
font-family: OPPOSANS; font-family: OPPOSANS;
} }
.price-text { .price-text {
font-size: 30rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;
color: $red; color: $red;
font-family: OPPOSANS; font-family: OPPOSANS;
&::before { &::before {
content: '¥'; content: '¥';
font-size: 30rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;
color: $red; color: $red;
} }
} }
.stock-text { .stock-text {
font-size: 26rpx; font-size: 26rpx;
color: #999999; color: #999999;
} }
} }
.modal-content { .modal-content {
padding: 0 20rpx; padding: 0 20rpx;
.modal-content-scroll { .modal-content-scroll {
max-height: 600rpx; max-height: 600rpx;
.label-text { .label-text {
font-size: 26rpx; font-size: 26rpx;
font-weight: 500; font-weight: 500;
} }
.buy-num-box { .buy-num-box {
height: 100rpx; height: 100rpx;
} }
.spec-btn { .spec-btn {
height: 60rpx; height: 60rpx;
min-width: 100rpx; min-width: 100rpx;
padding: 0 30rpx; padding: 0 30rpx;
background: #f4f4f4; background: #f4f4f4;
border-radius: 30rpx; border-radius: 30rpx;
color: #434343; color: #434343;
font-size: 26rpx; font-size: 26rpx;
margin-right: 10rpx; margin-right: 10rpx;
margin-bottom: 10rpx; margin-bottom: 10rpx;
} }
.disabled-btn { .disabled-btn {
font-weight: 400; font-weight: 400;
color: #c6c6c6; color: #c6c6c6;
background: #f8f8f8; background: #f8f8f8;
} }
} }
} }
} }
.iconBox {
width: fit-content;
height: fit-content;
padding: 2rpx 10rpx;
background-color: rgb(255, 242, 241);
color: #ff2621;
font-size: 24rpx;
margin-left: 5rpx;
}
.origin-price-text {
font-size: 26rpx;
font-weight: 400;
text-decoration: line-through;
color: $gray-c;
font-family: OPPOSANS;
&::before {
content: '¥';
}
}
</style> </style>

View File

@ -11,7 +11,7 @@ import { formatDate } from '@/sheep/util';
*/ */
export function formatSales(type, num) { export function formatSales(type, num) {
let prefix = type !== 'exact' && num < 10 ? '销量' : '已售'; let prefix = type !== 'exact' && num < 10 ? '销量' : '已售';
return formatNum(prefix, type, num) return formatNum(prefix, type, num);
} }
/** /**
@ -21,10 +21,9 @@ export function formatSales(type, num) {
* @return {string} 格式化后的销量字符串 * @return {string} 格式化后的销量字符串
*/ */
export function formatExchange(type, num) { export function formatExchange(type, num) {
return formatNum('已兑换', type, num) return formatNum('已兑换', type, num);
} }
/** /**
* 格式化库存 * 格式化库存
* @param {'exact' | any} type 格式类型exact=精确值其它=大致数量 * @param {'exact' | any} type 格式类型exact=精确值其它=大致数量
@ -32,7 +31,7 @@ export function formatExchange(type, num) {
* @return {string} 格式化后的销量字符串 * @return {string} 格式化后的销量字符串
*/ */
export function formatStock(type, num) { export function formatStock(type, num) {
return formatNum('库存', type, num) return formatNum('库存', type, num);
} }
/** /**
@ -43,7 +42,7 @@ export function formatStock(type, num) {
* @return {string} 格式化后的销量字符串 * @return {string} 格式化后的销量字符串
*/ */
export function formatNum(prefix, type, num) { export function formatNum(prefix, type, num) {
num = (num || 0); num = num || 0;
// 情况一:精确数值 // 情况一:精确数值
if (type === 'exact') { if (type === 'exact') {
return prefix + num; return prefix + num;
@ -67,7 +66,7 @@ export function formatPrice(e) {
} }
// 视频格式后缀列表 // 视频格式后缀列表
const VIDEO_SUFFIX_LIST = ['.avi', '.mp4'] const VIDEO_SUFFIX_LIST = ['.avi', '.mp4'];
/** /**
* 转换商品轮播的链接列表根据链接的后缀判断是视频链接还是图片链接 * 转换商品轮播的链接列表根据链接的后缀判断是视频链接还是图片链接
@ -76,12 +75,19 @@ const VIDEO_SUFFIX_LIST = ['.avi', '.mp4']
* @return {{src: string, type: 'video' | 'image' }[]} 转换后的链接列表 * @return {{src: string, type: 'video' | 'image' }[]} 转换后的链接列表
*/ */
export function formatGoodsSwiper(urlList) { export function formatGoodsSwiper(urlList) {
return urlList?.filter(url => url).map((url, key) => { return (
const isVideo = VIDEO_SUFFIX_LIST.some(suffix => url.includes(suffix)); urlList
const type = isVideo ? 'video' : 'image' ?.filter((url) => url)
const src = $url.cdn(url); .map((url, key) => {
return { type, src } const isVideo = VIDEO_SUFFIX_LIST.some((suffix) => url.includes(suffix));
}) || []; const type = isVideo ? 'video' : 'image';
const src = $url.cdn(url);
return {
type,
src,
};
}) || []
);
} }
/** /**
@ -94,9 +100,7 @@ export function formatOrderColor(order) {
if (order.status === 0) { if (order.status === 0) {
return 'info-color'; return 'info-color';
} }
if (order.status === 10 if (order.status === 10 || order.status === 20 || (order.status === 30 && !order.commentStatus)) {
|| order.status === 20
|| (order.status === 30 && !order.commentStatus)) {
return 'warning-color'; return 'warning-color';
} }
if (order.status === 30 && order.commentStatus) { if (order.status === 30 && order.commentStatus) {
@ -139,7 +143,7 @@ export function formatOrderStatus(order) {
*/ */
export function formatOrderStatusDescription(order) { export function formatOrderStatusDescription(order) {
if (order.status === 0) { if (order.status === 0) {
return `请在 ${ formatDate(order.payExpireTime) } 前完成支付`; return `请在 ${formatDate(order.payExpireTime)} 前完成支付`;
} }
if (order.status === 10) { if (order.status === 10) {
return '商家未发货,请耐心等待'; return '商家未发货,请耐心等待';
@ -162,24 +166,30 @@ export function formatOrderStatusDescription(order) {
* @param order 订单 * @param order 订单
*/ */
export function handleOrderButtons(order) { export function handleOrderButtons(order) {
order.buttons = [] order.buttons = [];
if (order.type === 3) { // 查看拼团 if (order.type === 3) {
// 查看拼团
order.buttons.push('combination'); order.buttons.push('combination');
} }
if (order.status === 20) { // 确认收货 if (order.status === 20) {
// 确认收货
order.buttons.push('confirm'); order.buttons.push('confirm');
} }
if (order.logisticsId > 0) { // 查看物流 if (order.logisticsId > 0) {
// 查看物流
order.buttons.push('express'); order.buttons.push('express');
} }
if (order.status === 0) { // 取消订单 / 发起支付 if (order.status === 0) {
// 取消订单 / 发起支付
order.buttons.push('cancel'); order.buttons.push('cancel');
order.buttons.push('pay'); order.buttons.push('pay');
} }
if (order.status === 30 && !order.commentStatus) { // 发起评价 if (order.status === 30 && !order.commentStatus) {
// 发起评价
order.buttons.push('comment'); order.buttons.push('comment');
} }
if (order.status === 40) { // 删除订单 if (order.status === 40) {
// 删除订单
order.buttons.push('delete'); order.buttons.push('delete');
} }
} }
@ -257,10 +267,12 @@ export function formatAfterSaleStatusDescription(afterSale) {
*/ */
export function handleAfterSaleButtons(afterSale) { export function handleAfterSaleButtons(afterSale) {
afterSale.buttons = []; afterSale.buttons = [];
if ([10, 20, 30].includes(afterSale.status)) { // 取消订单 if ([10, 20, 30].includes(afterSale.status)) {
// 取消订单
afterSale.buttons.push('cancel'); afterSale.buttons.push('cancel');
} }
if (afterSale.status === 20) { // 退货信息 if (afterSale.status === 20) {
// 退货信息
afterSale.buttons.push('delivery'); afterSale.buttons.push('delivery');
} }
} }
@ -324,7 +336,28 @@ function getDayjsTime(time) {
* @returns {string} 例如说 1.00 * @returns {string} 例如说 1.00
*/ */
export function fen2yuan(price) { export function fen2yuan(price) {
return (price / 100.0).toFixed(2) return (price / 100.0).toFixed(2);
}
/**
* 将分转成元
*
* 如果没有小数点则不展示小数点部分
*
* @param price 例如说 100
* @returns {string} 例如说 1
*/
export function fen2yuanSimple(price) {
return fen2yuan(price).replace(/\.?0+$/, '');
}
/**
* 将折扣百分比转化为打x者 x 部分
*
* @param discountPercent
*/
export function formatDiscountPercent(discountPercent) {
return (discountPercent / 10.0).toFixed(1).replace(/\.?0+$/, '');
} }
/** /**
@ -345,45 +378,122 @@ export function convertProductPropertyList(skus) {
let result = []; let result = [];
for (const sku of skus) { for (const sku of skus) {
if (!sku.properties) { if (!sku.properties) {
continue continue;
} }
for (const property of sku.properties) { for (const property of sku.properties) {
// ① 先处理属性 // ① 先处理属性
let resultProperty = result.find(item => item.id === property.propertyId) let resultProperty = result.find((item) => item.id === property.propertyId);
if (!resultProperty) { if (!resultProperty) {
resultProperty = { resultProperty = {
id: property.propertyId, id: property.propertyId,
name: property.propertyName, name: property.propertyName,
values: [] values: [],
} };
result.push(resultProperty) result.push(resultProperty);
} }
// ② 再处理属性值 // ② 再处理属性值
let resultValue = resultProperty.values.find(item => item.id === property.valueId) let resultValue = resultProperty.values.find((item) => item.id === property.valueId);
if (!resultValue) { if (!resultValue) {
resultProperty.values.push({ resultProperty.values.push({
id: property.valueId, id: property.valueId,
name: property.valueName name: property.valueName,
}) });
} }
} }
} }
return result; return result;
} }
/** export function appendSettlementProduct(spus, settlementInfos) {
* 格式化满减送活动的规则 if (!settlementInfos || settlementInfos.length === 0) {
* return;
* @param activity 活动信息
* @param rule 优惠规格
* @returns {string} 规格字符串
*/
export function formatRewardActivityRule(activity, rule) {
if (activity.conditionType === 10) {
return `${fen2yuan(rule.limit)} 元减 ${fen2yuan(rule.discountPrice)}`;
} }
if (activity.conditionType === 20) { for (const spu of spus) {
return `${rule.limit} 件减 ${fen2yuan(rule.discountPrice)}`; const settlementInfo = settlementInfos.find((info) => info.spuId === spu.id);
if (!settlementInfo) {
return;
}
// 选择价格最小的 SKU 设置到 SPU 上
const settlementSku = settlementInfo.skus
.filter((sku) => sku.promotionPrice > 0)
.reduce((prev, curr) => (prev.promotionPrice < curr.promotionPrice ? prev : curr));
if (settlementSku) {
spu.promotionType = settlementSku.promotionType;
spu.promotionPrice = settlementSku.promotionPrice;
}
// 设置【满减送】活动
if (settlementInfo.rewardActivity) {
spu.rewardActivity = settlementInfo.rewardActivity;
}
} }
return ''; }
// 获得满减送活动的规则描述group
export function getRewardActivityRuleGroupDescriptions(activity) {
if (!activity || !activity.rules || activity.rules.length === 0) {
return [];
}
const result = [
{ name: '满减', values: [] },
{ name: '赠品', values: [] },
{ name: '包邮', values: [] },
];
activity.rules.forEach((rule) => {
const conditionTypeStr =
activity.conditionType === 10 ? `${fen2yuanSimple(rule.limit)}` : `${rule.limit}`;
// 满减
if (rule.limit) {
result[0].values.push(`${conditionTypeStr}${fen2yuanSimple(rule.discountPrice)}`);
}
// 赠品
if (rule.point || (rule.giveCouponTemplateCounts && rule.giveCouponTemplateCounts.length > 0)) {
let tips = [];
if (rule.point) {
tips.push(`${rule.point} 积分`);
}
if (rule.giveCouponTemplateCounts && rule.giveCouponTemplateCounts.length > 0) {
tips.push(`${rule.giveCouponTemplateCounts.length} 张优惠券`);
}
result[1].values.push(`${conditionTypeStr} ${tips.join('、')}`);
}
// 包邮
if (rule.freeDelivery) {
result[2].values.push(`${conditionTypeStr} 包邮`);
}
});
// 移除 values 为空的元素
result.forEach((item) => {
if (item.values.length === 0) {
result.splice(result.indexOf(item), 1);
}
});
return result;
}
// 获得满减送活动的规则描述item
export function getRewardActivityRuleItemDescriptions(activity) {
if (!activity || !activity.rules || activity.rules.length === 0) {
return [];
}
const result = [];
activity.rules.forEach((rule) => {
const conditionTypeStr =
activity.conditionType === 10 ? `${fen2yuanSimple(rule.limit)}` : `${rule.limit}`;
// 满减
if (rule.limit) {
result.push(`${conditionTypeStr}${fen2yuanSimple(rule.discountPrice)}`);
}
// 赠品
if (rule.point) {
result.push(`${conditionTypeStr}${rule.point}积分`);
}
if (rule.giveCouponTemplateCounts && rule.giveCouponTemplateCounts.length > 0) {
result.push(`${conditionTypeStr}${rule.giveCouponTemplateCounts.length}张优惠券`);
}
// 包邮
if (rule.freeDelivery) {
result.push(`${conditionTypeStr}包邮`);
}
});
return result;
} }

View File

@ -35,7 +35,7 @@ export default class SheepPay {
}, },
mock: () => { mock: () => {
this.mockPay(); this.mockPay();
} },
}, },
WechatMiniProgram: { WechatMiniProgram: {
wechat: () => { wechat: () => {
@ -49,7 +49,7 @@ export default class SheepPay {
}, },
mock: () => { mock: () => {
this.mockPay(); this.mockPay();
} },
}, },
App: { App: {
wechat: () => { wechat: () => {
@ -63,7 +63,7 @@ export default class SheepPay {
}, },
mock: () => { mock: () => {
this.mockPay(); this.mockPay();
} },
}, },
H5: { H5: {
wechat: () => { wechat: () => {
@ -77,7 +77,7 @@ export default class SheepPay {
}, },
mock: () => { mock: () => {
this.mockPay(); this.mockPay();
} },
}, },
}; };
return payAction[sheep.$platform.name][this.payment](); return payAction[sheep.$platform.name][this.payment]();
@ -89,7 +89,7 @@ export default class SheepPay {
let data = { let data = {
id: this.id, id: this.id,
channelCode: channel, channelCode: channel,
channelExtras: {} channelExtras: {},
}; };
// 特殊逻辑:微信公众号、小程序支付时,必须传入 openid // 特殊逻辑:微信公众号、小程序支付时,必须传入 openid
if (['wx_pub', 'wx_lite'].includes(channel)) { if (['wx_pub', 'wx_lite'].includes(channel)) {
@ -108,8 +108,11 @@ export default class SheepPay {
// 失败时 // 失败时
if (res.code !== 0 && res.msg.indexOf('无效的openid') >= 0) { if (res.code !== 0 && res.msg.indexOf('无效的openid') >= 0) {
// 特殊逻辑:微信公众号、小程序支付时,必须传入 openid 不正确的情况 // 特殊逻辑:微信公众号、小程序支付时,必须传入 openid 不正确的情况
if (res.msg.indexOf('无效的openid') >= 0 // 获取的 openid 不正确时,或者随便输入了个 openid if (
|| res.msg.indexOf('下单账号与支付账号不一致') >= 0) { // https://developers.weixin.qq.com/community/develop/doc/00008c53c347804beec82aed051c00 res.msg.indexOf('无效的openid') >= 0 || // 获取的 openid 不正确时,或者随便输入了个 openid
res.msg.indexOf('下单账号与支付账号不一致') >= 0
) {
// https://developers.weixin.qq.com/community/develop/doc/00008c53c347804beec82aed051c00
this.bindWeixin(); this.bindWeixin();
} }
} }
@ -133,30 +136,34 @@ export default class SheepPay {
}, },
fail: (error) => { fail: (error) => {
if (error.errMsg.indexOf('chooseWXPay:没有此SDK或暂不支持此SDK模拟') >= 0) { if (error.errMsg.indexOf('chooseWXPay:没有此SDK或暂不支持此SDK模拟') >= 0) {
sheep.$helper.toast('发起微信支付失败,原因:可能是微信开发者工具不支持,建议使用微信打开网页后支付'); sheep.$helper.toast(
return '发起微信支付失败,原因:可能是微信开发者工具不支持,建议使用微信打开网页后支付',
);
return;
} }
this.payResult('fail'); this.payResult('fail');
}, },
}); });
} }
// 浏览器微信 H5 支付 TODO 芋艿:待接入 // 浏览器微信 H5 支付 TODO 芋艿:待接入注意H5 支付是给普通浏览器,不是微信公众号的支付,绝大多数人用不到,可以忽略)
async wechatWapPay() { async wechatWapPay() {
const { error, data } = await this.prepay(); const { error, data } = await this.prepay();
if (error === 0) { if (error === 0) {
const redirect_url = `${getRootUrl()}pages/pay/result?id=${this.id}&payment=${this.payment}&orderType=${this.orderType}`; const redirect_url = `${getRootUrl()}pages/pay/result?id=${this.id}&payment=${
this.payment
}&orderType=${this.orderType}`;
location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`; location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`;
} }
} }
// 支付链接 TODO 芋艿:待接入 // 支付链接(支付宝 wap 支付)
async redirectPay() { async redirectPay() {
let { error, data } = await this.prepay(); let { code, data } = await this.prepay('alipay_wap');
if (error === 0) { if (code !== 0) {
const redirect_url = `${getRootUrl()}pages/pay/result?id=${this.id}&payment=${this.payment}&orderType=${this.orderType}`; return;
location.href = data.pay_data + encodeURIComponent(redirect_url);
} }
location.href = data.displayContent;
} }
// #endif // #endif
@ -202,26 +209,26 @@ export default class SheepPay {
code === 0 && this.payResult('success'); code === 0 && this.payResult('success');
} }
// 支付宝复制链接支付 TODO 芋艿:待接入 // 支付宝复制链接支付(通过支付宝 wap 支付实现)
async copyPayLink() { async copyPayLink() {
let that = this; let { code, data } = await this.prepay('alipay_wap');
let { error, data } = await this.prepay(); if (code !== 0) {
if (error === 0) { return;
// 引入showModal 点击确认 复制链接;
uni.showModal({
title: '支付宝支付',
content: '复制链接到外部浏览器',
confirmText: '复制链接',
success: (res) => {
if (res.confirm) {
sheep.$helper.copyText(data.pay_data);
}
},
});
} }
// 引入 showModal 点击确认:复制链接;
uni.showModal({
title: '支付宝支付',
content: '复制链接到外部浏览器',
confirmText: '复制链接',
success: (res) => {
if (res.confirm) {
sheep.$helper.copyText(data.displayContent);
}
},
});
} }
// 支付宝支付 TODO 芋艿:待接入 // 支付宝支付App TODO 芋艿:待接入【暂时没打包 app所以没接入一般人用不到】
async alipay() { async alipay() {
let that = this; let that = this;
const { error, data } = await this.prepay(); const { error, data } = await this.prepay();
@ -243,7 +250,7 @@ export default class SheepPay {
} }
} }
// 微信支付 TODO 芋艿:待接入 // 微信支付App TODO 芋艿:待接入:待接入【暂时没打包 app所以没接入一般人用不到】
async wechatAppPay() { async wechatAppPay() {
let that = this; let that = this;
let { error, data } = await this.prepay(); let { error, data } = await this.prepay();
@ -263,11 +270,7 @@ export default class SheepPay {
// 支付结果跳转,success:成功fail:失败 // 支付结果跳转,success:成功fail:失败
payResult(resultType) { payResult(resultType) {
sheep.$router.redirect('/pages/pay/result', { goPayResult(this.id, this.orderType, resultType);
id: this.id,
orderType: this.orderType,
payState: resultType
});
} }
// 引导绑定微信 // 引导绑定微信
@ -282,7 +285,6 @@ export default class SheepPay {
}, },
}); });
} }
} }
export function getPayMethods(channels) { export function getPayMethods(channels) {
@ -316,23 +318,28 @@ export function getPayMethods(channels) {
title: '模拟支付', title: '模拟支付',
value: 'mock', value: 'mock',
disabled: true, disabled: true,
} },
]; ];
const platform = sheep.$platform.name const platform = sheep.$platform.name;
// 1. 处理【微信支付】 // 1. 处理【微信支付】
const wechatMethod = payMethods[0]; const wechatMethod = payMethods[0];
if ((platform === 'WechatOfficialAccount' && channels.includes('wx_pub')) if (
|| (platform === 'WechatMiniProgram' && channels.includes('wx_lite')) (platform === 'WechatOfficialAccount' && channels.includes('wx_pub')) ||
|| (platform === 'App' && channels.includes('wx_app'))) { (platform === 'WechatMiniProgram' && channels.includes('wx_lite')) ||
(platform === 'App' && channels.includes('wx_app'))
) {
wechatMethod.disabled = false; wechatMethod.disabled = false;
} }
// 2. 处理【支付宝支付】 // 2. 处理【支付宝支付】
const alipayMethod = payMethods[1]; const alipayMethod = payMethods[1];
if ((platform === 'WechatOfficialAccount' && channels.includes('alipay_wap')) if (
|| platform === 'WechatMiniProgram' && channels.includes('alipay_wap') (platform === 'H5' && channels.includes('alipay_wap')) ||
|| platform === 'App' && channels.includes('alipay_app')) { (platform === 'WechatOfficialAccount' && channels.includes('alipay_wap')) ||
(platform === 'WechatMiniProgram' && channels.includes('alipay_wap')) ||
(platform === 'App' && channels.includes('alipay_app'))
) {
alipayMethod.disabled = false; alipayMethod.disabled = false;
} }
// 3. 处理【余额支付】 // 3. 处理【余额支付】
@ -348,3 +355,12 @@ export function getPayMethods(channels) {
} }
return payMethods; return payMethods;
} }
// 支付结果跳转,success:成功fail:失败
export function goPayResult(id, orderType, resultType) {
sheep.$router.redirect('/pages/pay/result', {
id,
orderType,
payState: resultType,
});
}

View File

@ -33,78 +33,100 @@ export const getTerminal = () => {
// ========== MALL - 营销模块 ========== // ========== MALL - 营销模块 ==========
import dayjs from "dayjs"; import dayjs from 'dayjs';
/** /**
* 优惠类型枚举 * 优惠类型枚举
*/ */
export const PromotionDiscountTypeEnum = { export const PromotionDiscountTypeEnum = {
PRICE: { PRICE: {
type: 1, type: 1,
name: '满减' name: '满减',
}, },
PERCENT: { PERCENT: {
type: 2, type: 2,
name: '折扣' name: '折扣',
} },
} };
/** /**
* 优惠劵模板的有限期类型的枚举 * 优惠劵模板的有限期类型的枚举
*/ */
export const CouponTemplateValidityTypeEnum = { export const CouponTemplateValidityTypeEnum = {
DATE: { DATE: {
type: 1, type: 1,
name: '固定日期可用' name: '固定日期可用',
}, },
TERM: { TERM: {
type: 2, type: 2,
name: '领取之后可用' name: '领取之后可用',
} },
} };
/** /**
* 营销的商品范围枚举 * 营销的商品范围枚举
*/ */
export const PromotionProductScopeEnum = { export const PromotionProductScopeEnum = {
ALL: { ALL: {
scope: 1, scope: 1,
name: '通用劵' name: '通用劵',
}, },
SPU: { SPU: {
scope: 2, scope: 2,
name: '商品劵' name: '商品劵',
}, },
CATEGORY: { CATEGORY: {
scope: 3, scope: 3,
name: '品类劵' name: '品类劵',
} },
} };
// 时间段的状态枚举 // 时间段的状态枚举
export const TimeStatusEnum = { export const TimeStatusEnum = {
WAIT_START: '即将开始', WAIT_START: '即将开始',
STARTED: '进行中', STARTED: '进行中',
END: '已结束', END: '已结束',
} };
/** /**
* 微信小程序的订阅模版 * 微信小程序的订阅模版
*/ */
export const WxaSubscribeTemplate = { export const WxaSubscribeTemplate = {
TRADE_ORDER_DELIVERY: "订单发货通知", TRADE_ORDER_DELIVERY: '订单发货通知',
PROMOTION_COMBINATION_SUCCESS: "拼团结果通知", PROMOTION_COMBINATION_SUCCESS: '拼团结果通知',
PAY_WALLET_RECHARGER_SUCCESS: "充值成功通知", PAY_WALLET_RECHARGER_SUCCESS: '充值成功通知',
} };
export const PromotionActivityTypeEnum = {
NORMAL: {
type: 0,
name: '普通',
},
SECKILL: {
type: 1,
name: '秒杀',
},
BARGAIN: {
type: 2,
name: '砍价',
},
COMBINATION: {
type: 3,
name: '拼团',
},
POINT: {
type: 4,
name: '积分商城',
},
};
export const getTimeStatusEnum = (startTime, endTime) => { export const getTimeStatusEnum = (startTime, endTime) => {
const now = dayjs(); const now = dayjs();
if (now.isBefore(startTime)) { if (now.isBefore(startTime)) {
return TimeStatusEnum.WAIT_START; return TimeStatusEnum.WAIT_START;
} else if (now.isAfter(endTime)) { } else if (now.isAfter(endTime)) {
return TimeStatusEnum.END; return TimeStatusEnum.END;
} else { } else {
return TimeStatusEnum.STARTED; return TimeStatusEnum.STARTED;
} }
} };