feat: 初始化项目配置和样式- 添加项目环境变量配置文件 (.env)
- 创建代码忽略文件列表 (.gitignore) - 设置代码格式化规则 (.prettierrc)- 添加各种样式文件,包括背景、边框、按钮、卡片等组件样式 - 创建暗黑主题样式文件 - 添加表单和代码样式文件
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
<!-- 评价 -->
|
||||
<template>
|
||||
<s-layout title="评价">
|
||||
<view>
|
||||
<view v-for="(item, index) in state.orderInfo.items" :key="item.id">
|
||||
<view>
|
||||
<view class="commont-from-wrap">
|
||||
<!-- 评价商品 -->
|
||||
<s-goods-item
|
||||
:img="item.picUrl"
|
||||
:title="item.spuName"
|
||||
:skuText="item.properties.map((property) => property.valueName).join(' ')"
|
||||
:price="item.payPrice"
|
||||
:num="item.count"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<!-- 评分 -->
|
||||
<view class="star-box ss-flex ss-col-center">
|
||||
<view class="star-title ss-m-r-40">商品质量</view>
|
||||
<uni-rate v-model="state.commentList[index].descriptionScores" />
|
||||
</view>
|
||||
<view class="star-box ss-flex ss-col-center">
|
||||
<view class="star-title ss-m-r-40">服务态度</view>
|
||||
<uni-rate v-model="state.commentList[index].benefitScores" />
|
||||
</view>
|
||||
<!-- 评价 -->
|
||||
<view class="area-box">
|
||||
<uni-easyinput
|
||||
:inputBorder="false"
|
||||
type="textarea"
|
||||
maxlength="120"
|
||||
autoHeight
|
||||
v-model="state.commentList[index].content"
|
||||
placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~"
|
||||
/>
|
||||
<view class="img-box">
|
||||
<s-uploader
|
||||
v-model:url="state.commentList[index].images"
|
||||
fileMediatype="image"
|
||||
limit="9"
|
||||
mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }"
|
||||
@success="(payload) => uploadSuccess(payload, index)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="checkbox-container">
|
||||
<checkbox-group @change="(event) => toggleAnonymous(index, event)">
|
||||
<label>
|
||||
<checkbox value="anonymousChecked" />
|
||||
匿名评论
|
||||
</label>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<su-fixed bottom placeholder>
|
||||
<view class="foot_box ss-flex ss-row-center ss-col-center">
|
||||
<button class="ss-reset-button post-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onSubmit">
|
||||
发布
|
||||
</button>
|
||||
</view>
|
||||
</su-fixed>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { reactive } from 'vue';
|
||||
import OrderApi from '@/sheep/api/trade/order';
|
||||
|
||||
const state = reactive({
|
||||
orderInfo: {},
|
||||
commentList: [],
|
||||
id: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* 切换是否匿名
|
||||
*
|
||||
* @param commentIndex 当前评论下标
|
||||
* @param event 复选框事件
|
||||
*/
|
||||
function toggleAnonymous(commentIndex, event) {
|
||||
state.commentList[commentIndex].anonymous = event.detail.value[0] === 'anonymousChecked';
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布评论
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function onSubmit() {
|
||||
// 顺序提交评论
|
||||
for (const comment of state.commentList) {
|
||||
await OrderApi.createOrderItemComment(comment);
|
||||
}
|
||||
// 都评论好,返回
|
||||
sheep.$router.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片添加到表单
|
||||
*
|
||||
* @param payload 上传成功后的回调数据
|
||||
* @param commentIndex 当前评论的下标
|
||||
*/
|
||||
function uploadSuccess(payload, commentIndex) {
|
||||
state.commentList[commentIndex].picUrls = payload.tempFilePaths;
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (!options.id) {
|
||||
sheep.$helper.toast(`缺少订单信息,请检查`);
|
||||
return;
|
||||
}
|
||||
state.id = options.id;
|
||||
|
||||
const { code, data } = await OrderApi.getOrderDetail(state.id);
|
||||
if (code !== 0) {
|
||||
sheep.$helper.toast('无待评价订单');
|
||||
return;
|
||||
}
|
||||
// 处理评论
|
||||
data.items.forEach((item) => {
|
||||
state.commentList.push({
|
||||
anonymous: false,
|
||||
orderItemId: item.id,
|
||||
descriptionScores: 5,
|
||||
benefitScores: 5,
|
||||
content: '',
|
||||
picUrls: [],
|
||||
});
|
||||
});
|
||||
state.orderInfo = data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 评价商品
|
||||
.goods-card {
|
||||
margin: 10rpx 0;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
// 评论,选择图片
|
||||
.form-item {
|
||||
background: #fff;
|
||||
|
||||
.star-box {
|
||||
height: 100rpx;
|
||||
padding: 0 25rpx;
|
||||
}
|
||||
|
||||
.star-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.area-box {
|
||||
width: 690rpx;
|
||||
min-height: 306rpx;
|
||||
background: rgba(249, 250, 251, 1);
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin: auto;
|
||||
|
||||
.img-box {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.post-btn {
|
||||
width: 690rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
color: rgba(#fff, 0.9);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,168 @@
|
||||
<!-- 商品评论的分页 -->
|
||||
<template>
|
||||
<s-layout title="全部评论">
|
||||
<su-tabs
|
||||
:list="state.type"
|
||||
:scrollable="false"
|
||||
@change="onTabsChange"
|
||||
:current="state.currentTab"
|
||||
/>
|
||||
<!-- 评论列表 -->
|
||||
<view class="ss-m-t-20">
|
||||
<view class="list-item" v-for="item in state.pagination.list" :key="item">
|
||||
<comment-item :item="item" />
|
||||
</view>
|
||||
</view>
|
||||
<s-empty v-if="state.pagination.total === 0" text="暂无数据" icon="/static/data-empty.png" />
|
||||
<!-- 下拉 -->
|
||||
<uni-load-more
|
||||
icon-type="auto"
|
||||
v-if="state.pagination.total > 0"
|
||||
:status="state.loadStatus"
|
||||
:content-text="{
|
||||
contentdown: '上拉加载更多',
|
||||
}"
|
||||
@tap="loadMore"
|
||||
/>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CommentApi from '@/sheep/api/product/comment';
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
|
||||
import { reactive } from 'vue';
|
||||
import _ from 'lodash-es';
|
||||
import commentItem from '../components/detail/comment-item.vue';
|
||||
|
||||
const state = reactive({
|
||||
id: 0, // 商品 SPU 编号
|
||||
type: [
|
||||
{ type: 0, name: '全部' },
|
||||
{ type: 1, name: '好评' },
|
||||
{ type: 2, name: '中评' },
|
||||
{ type: 3, name: '差评' },
|
||||
],
|
||||
currentTab: 0, // 选中的 TAB
|
||||
loadStatus: '',
|
||||
pagination: {
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNo: 1,
|
||||
pageSize: 8,
|
||||
},
|
||||
});
|
||||
|
||||
// 切换选项卡
|
||||
function onTabsChange(e) {
|
||||
state.currentTab = e.index;
|
||||
// 加载列表
|
||||
state.pagination.pageNo = 1;
|
||||
state.pagination.list = [];
|
||||
state.pagination.total = 0;
|
||||
getList();
|
||||
}
|
||||
|
||||
async function getList() {
|
||||
// 加载列表
|
||||
state.loadStatus = 'loading';
|
||||
let res = await CommentApi.getCommentPage(
|
||||
state.id,
|
||||
state.pagination.pageNo,
|
||||
state.pagination.pageSize,
|
||||
state.type[state.currentTab].type,
|
||||
);
|
||||
if (res.code !== 0) {
|
||||
return;
|
||||
}
|
||||
// 合并列表
|
||||
state.pagination.list = _.concat(state.pagination.list, res.data.list);
|
||||
state.pagination.total = res.data.total;
|
||||
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
function loadMore() {
|
||||
if (state.loadStatus === 'noMore') {
|
||||
return;
|
||||
}
|
||||
state.pagination.pageNo++;
|
||||
getList();
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
state.id = options.id;
|
||||
getList();
|
||||
});
|
||||
|
||||
// 上拉加载更多
|
||||
onReachBottom(() => {
|
||||
loadMore();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-item {
|
||||
padding: 32rpx 30rpx 20rpx 20rpx;
|
||||
background: #fff;
|
||||
|
||||
.avatar {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.create-time {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #c4c4c4;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: #666666;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.content-img {
|
||||
width: 174rpx;
|
||||
height: 174rpx;
|
||||
}
|
||||
|
||||
.cicon-info-o {
|
||||
font-size: 26rpx;
|
||||
color: #c4c4c4;
|
||||
}
|
||||
|
||||
.foot-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-box {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
background: #fff;
|
||||
border-top: 2rpx solid #eee;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
width: 130rpx;
|
||||
height: 62rpx;
|
||||
background: #eeeeee;
|
||||
border-radius: 31rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
border: 1px solid #e5e5e5;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!-- 商品评论项 -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 用户评论 -->
|
||||
<view class="user ss-flex ss-m-b-14">
|
||||
<view class="ss-m-r-20 ss-flex">
|
||||
<image class="avatar" :src="item.userAvatar"></image>
|
||||
</view>
|
||||
<view class="nickname ss-m-r-20">{{ item.userNickname }}</view>
|
||||
<view class="">
|
||||
<uni-rate :readonly="true" v-model="item.scores" size="18" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="content"> {{ item.content }} </view>
|
||||
<view class="ss-m-t-24" v-if="item.picUrls?.length">
|
||||
<scroll-view class="scroll-box" scroll-x scroll-anchoring>
|
||||
<view class="ss-flex">
|
||||
<view v-for="(picUrl, index) in item.picUrls" :key="picUrl" class="ss-m-r-10">
|
||||
<su-image
|
||||
class="content-img"
|
||||
isPreview
|
||||
:previewList="item.picUrls"
|
||||
:current="index"
|
||||
:src="picUrl"
|
||||
:height="120"
|
||||
:width="120"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- 商家回复 -->
|
||||
<view class="ss-m-t-20 reply-box" v-if="item.replyTime">
|
||||
<view class="reply-title">商家回复:</view>
|
||||
<view class="reply-content">{{ item.replyContent }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 636rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.reply-box {
|
||||
position: relative;
|
||||
background: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.reply-title {
|
||||
position: absolute;
|
||||
left: 16rpx;
|
||||
top: 16rpx;
|
||||
font-weight: 400;
|
||||
font-size: 26rpx;
|
||||
line-height: 40rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.reply-content {
|
||||
text-indent: 128rpx;
|
||||
font-weight: 400;
|
||||
font-size: 26rpx;
|
||||
line-height: 40rpx;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<su-fixed bottom placeholder :val="44">
|
||||
<view>
|
||||
<view v-for="activity in props.activityList" :key="activity.id">
|
||||
<view
|
||||
class="activity-box ss-p-x-38 ss-flex ss-row-between ss-col-center"
|
||||
:class="activity.type === 1 ? 'seckill-box' : 'groupon-box'"
|
||||
>
|
||||
<view class="activity-title ss-flex">
|
||||
<view class="ss-m-r-16">
|
||||
<image
|
||||
v-if="activity.type === 1"
|
||||
:src="sheep.$url.static('/static/img/shop/goods/seckill-icon.png')"
|
||||
class="activity-icon"
|
||||
/>
|
||||
<image
|
||||
v-else-if="activity.type === 3"
|
||||
:src="sheep.$url.static('/static/img/shop/goods/groupon-icon.png')"
|
||||
class="activity-icon"
|
||||
/>
|
||||
</view>
|
||||
<view>该商品正在参与{{ activity.name }}活动</view>
|
||||
</view>
|
||||
<button class="ss-reset-button activity-go" @tap="onActivity(activity)"> GO </button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</su-fixed>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
|
||||
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');
|
||||
|
||||
const props = defineProps({
|
||||
activityList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function onActivity(activity) {
|
||||
const type = activity.type;
|
||||
const typePath = type === 1 ? 'seckill' : type === 3 ? 'groupon' : undefined;
|
||||
if (!typePath) {
|
||||
return;
|
||||
}
|
||||
sheep.$router.go(`/pages/goods/${typePath}`, {
|
||||
id: activity.id,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
//秒杀卡片
|
||||
.seckill-box {
|
||||
background: v-bind(seckillBg) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.groupon-box {
|
||||
background: v-bind(grouponBg) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<!-- SKU 选择的提示框 -->
|
||||
<detail-cell label="选择" :value="value" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import detailCell from './detail-cell.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
sku: {
|
||||
type: Object
|
||||
}
|
||||
});
|
||||
const value = computed(() => {
|
||||
if (!props.sku?.id) {
|
||||
return '请选择商品规格';
|
||||
}
|
||||
let str = '';
|
||||
props.sku.properties.forEach(property => {
|
||||
str += property.propertyName + ':' + property.valueName + ' ';
|
||||
});
|
||||
return str;
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!-- 商品详情:cell 组件 -->
|
||||
<template>
|
||||
<view class="detail-cell-wrap ss-flex ss-col-center ss-row-between" @tap="onClick">
|
||||
<view class="label-text">{{ label }}</view>
|
||||
<view class="cell-content ss-line-1 ss-flex-1">{{ value }}</view>
|
||||
<button class="ss-reset-button">
|
||||
<text class="_icon-forward right-forwrad-icon"></text>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 详情 cell
|
||||
*
|
||||
*/
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['click']);
|
||||
|
||||
// 点击
|
||||
const onClick = () => {
|
||||
emits('click');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-cell-wrap {
|
||||
padding: 10rpx 20rpx;
|
||||
// min-height: 60rpx;
|
||||
|
||||
.label-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $dark-9;
|
||||
margin-right: 38rpx;
|
||||
}
|
||||
|
||||
.cell-content {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $dark-6;
|
||||
}
|
||||
|
||||
.right-forwrad-icon {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $dark-9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<!-- 商品评论的卡片 -->
|
||||
<template>
|
||||
<view class="detail-comment-card bg-white">
|
||||
<view class="card-header ss-flex ss-col-center ss-row-between ss-p-b-30">
|
||||
<view class="ss-flex ss-col-center">
|
||||
<view class="line"></view>
|
||||
<view class="title ss-m-l-20 ss-m-r-10">评价</view>
|
||||
<view class="des">({{ state.total }})</view>
|
||||
</view>
|
||||
<view
|
||||
class="ss-flex ss-col-center"
|
||||
@tap="sheep.$router.go('/pages/goods/comment/list', { id: goodsId })"
|
||||
v-if="state.commentList.length > 0"
|
||||
>
|
||||
<button class="ss-reset-button more-btn">查看全部</button>
|
||||
<text class="cicon-forward" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 评论列表 -->
|
||||
<view class="card-content">
|
||||
<view class="comment-box ss-p-y-30" v-for="item in state.commentList" :key="item.id">
|
||||
<comment-item :item="item" />
|
||||
</view>
|
||||
<s-empty
|
||||
v-if="state.commentList.length === 0"
|
||||
paddingTop="0"
|
||||
icon="/static/comment-empty.png"
|
||||
text="期待您的第一个评价"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, onBeforeMount } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import CommentApi from '@/sheep/api/product/comment';
|
||||
import commentItem from './comment-item.vue';
|
||||
|
||||
const props = defineProps({
|
||||
goodsId: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
commentList: [], // 评论列表,只展示最近的 3 条
|
||||
total: 0, // 总评论数
|
||||
});
|
||||
|
||||
async function getComment(id) {
|
||||
const { data } = await CommentApi.getCommentPage(id, 1, 3, 0);
|
||||
state.commentList = data.list;
|
||||
state.total = data.total;
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getComment(props.goodsId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-comment-card {
|
||||
margin: 0 20rpx 20rpx 20rpx;
|
||||
padding: 20rpx 20rpx 0 20rpx;
|
||||
.card-header {
|
||||
.line {
|
||||
width: 6rpx;
|
||||
height: 30rpx;
|
||||
background: linear-gradient(180deg, var(--ui-BG-Main) 0%, var(--ui-BG-Main-gradient) 100%);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.des {
|
||||
font-size: 24rpx;
|
||||
color: $dark-9;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
font-size: 24rpx;
|
||||
color: var(--ui-BG-Main);
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.cicon-forward {
|
||||
font-size: 24rpx;
|
||||
line-height: normal;
|
||||
color: var(--ui-BG-Main);
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.comment-box {
|
||||
border-bottom: 2rpx solid #eeeeee;
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<view class="detail-content-card bg-white ss-m-x-20 ss-p-t-20">
|
||||
<view class="card-header ss-flex ss-col-center ss-m-b-30 ss-m-l-20">
|
||||
<view class="line"></view>
|
||||
<view class="title ss-m-l-20 ss-m-r-20">详情</view>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<mp-html :content="content"></mp-html>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-content-card {
|
||||
.card-header {
|
||||
.line {
|
||||
width: 6rpx;
|
||||
height: 30rpx;
|
||||
background: linear-gradient(180deg, var(--ui-BG-Main) 0%, var(--ui-BG-Main-gradient) 100%);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.des {
|
||||
font-size: 24rpx;
|
||||
color: $dark-9;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
font-size: 24rpx;
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep() {
|
||||
image {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,257 @@
|
||||
<!-- 商品详情:商品/评价/详情的 nav -->
|
||||
<template>
|
||||
<su-fixed alway :bgStyles="{ background: '#fff' }" :val="0" noNav opacity :placeholder="false">
|
||||
<su-status-bar />
|
||||
<view
|
||||
class="ui-bar ss-flex ss-col-center ss-row-between ss-p-x-20"
|
||||
:style="[{ height: sys_navBar - sys_statusBar + 'px' }]"
|
||||
>
|
||||
<!-- 左 -->
|
||||
<view class="icon-box ss-flex">
|
||||
<view class="icon-button icon-button-left ss-flex ss-row-center" @tap="onClickLeft">
|
||||
<text class="sicon-back" v-if="hasHistory" />
|
||||
<text class="sicon-home" v-else />
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="icon-button icon-button-right ss-flex ss-row-center" @tap="onClickRight">
|
||||
<text class="sicon-more" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 中 -->
|
||||
<view class="detail-tab-card ss-flex-1" :style="[{ opacity: state.tabOpacityVal }]">
|
||||
<view class="tab-box ss-flex ss-col-center ss-row-around">
|
||||
<view
|
||||
class="tab-item ss-flex-1 ss-flex ss-row-center ss-col-center"
|
||||
v-for="item in state.tabList"
|
||||
:key="item.value"
|
||||
@tap="onTab(item)"
|
||||
>
|
||||
<view class="tab-title" :class="state.curTab === item.value ? 'cur-tab-title' : ''">
|
||||
{{ item.label }}
|
||||
</view>
|
||||
<view v-show="state.curTab === item.value" class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef MP -->
|
||||
<view :style="[capsuleStyle]"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</su-fixed>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import { onPageScroll } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import throttle from '@/sheep/helper/throttle.js';
|
||||
import { showMenuTools } from '@/sheep/hooks/useModal';
|
||||
|
||||
const sys_statusBar = sheep.$platform.device.statusBarHeight;
|
||||
const sys_navBar = sheep.$platform.navbar;
|
||||
const capsuleStyle = {
|
||||
width: sheep.$platform.capsule.width + 'px',
|
||||
height: sheep.$platform.capsule.height + 'px',
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
tabOpacityVal: 0,
|
||||
curTab: 'goods',
|
||||
tabList: [
|
||||
{
|
||||
label: '商品',
|
||||
value: 'goods',
|
||||
to: 'detail-swiper-selector',
|
||||
},
|
||||
{
|
||||
label: '评价',
|
||||
value: 'comment',
|
||||
to: 'detail-comment-selector',
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
value: 'detail',
|
||||
to: 'detail-content-selector',
|
||||
},
|
||||
],
|
||||
});
|
||||
const emits = defineEmits(['clickLeft']);
|
||||
const hasHistory = sheep.$router.hasHistory();
|
||||
|
||||
function onClickLeft() {
|
||||
if (hasHistory) {
|
||||
sheep.$router.back();
|
||||
} else {
|
||||
sheep.$router.go('/pages/index/index');
|
||||
}
|
||||
emits('clickLeft');
|
||||
}
|
||||
|
||||
function onClickRight() {
|
||||
showMenuTools();
|
||||
}
|
||||
|
||||
let commentCard = {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
function getCommentCardNode() {
|
||||
return new Promise((res, rej) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.select('.detail-comment-selector')
|
||||
.boundingClientRect((data) => {
|
||||
if (data) {
|
||||
commentCard.top = data.top;
|
||||
commentCard.bottom = data.top + data.height;
|
||||
res(data);
|
||||
} else {
|
||||
res(null);
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
function onTab(tab) {
|
||||
let scrollTop = 0;
|
||||
if (tab.value === 'comment') {
|
||||
scrollTop = commentCard.top - sys_navBar + 1;
|
||||
} else if (tab.value === 'detail') {
|
||||
scrollTop = commentCard.bottom - sys_navBar + 1;
|
||||
}
|
||||
uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: 200,
|
||||
});
|
||||
}
|
||||
|
||||
onPageScroll((e) => {
|
||||
state.tabOpacityVal = e.scrollTop > sheep.$platform.navbar ? 1 : e.scrollTop * 0.01;
|
||||
if (commentCard.top === 0) {
|
||||
throttle(() => {
|
||||
getCommentCardNode();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (e.scrollTop < commentCard.top - sys_navBar) {
|
||||
state.curTab = 'goods';
|
||||
} else if (
|
||||
e.scrollTop >= commentCard.top - sys_navBar &&
|
||||
e.scrollTop <= commentCard.bottom - sys_navBar
|
||||
) {
|
||||
state.curTab = 'comment';
|
||||
} else {
|
||||
state.curTab = 'detail';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-box {
|
||||
box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
|
||||
border-radius: 30rpx;
|
||||
width: 134rpx;
|
||||
height: 56rpx;
|
||||
margin-left: 8rpx;
|
||||
border: 1px solid rgba(#fff, 0.4);
|
||||
.line {
|
||||
width: 2rpx;
|
||||
height: 24rpx;
|
||||
background: #e5e5e7;
|
||||
}
|
||||
.sicon-back {
|
||||
font-size: 32rpx;
|
||||
color: #000;
|
||||
}
|
||||
.sicon-home {
|
||||
font-size: 32rpx;
|
||||
color: #000;
|
||||
}
|
||||
.sicon-more {
|
||||
font-size: 32rpx;
|
||||
color: #000;
|
||||
}
|
||||
.icon-button {
|
||||
width: 67rpx;
|
||||
height: 56rpx;
|
||||
&-left:hover {
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border-radius: 30rpx 0px 0px 30rpx;
|
||||
}
|
||||
&-right:hover {
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border-radius: 0px 30rpx 30rpx 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.left-box {
|
||||
position: relative;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.circle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(#fff, 0.6);
|
||||
border: 1rpx solid #ebebeb;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
position: relative;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.circle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(#ffffff, 0.6);
|
||||
border: 1rpx solid #ebebeb;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
.detail-tab-card {
|
||||
width: 50%;
|
||||
.tab-item {
|
||||
height: 80rpx;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
|
||||
.tab-title {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.cur-tab-title {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 10rpx;
|
||||
background-color: var(--ui-BG-Main);
|
||||
z-index: 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- 秒杀商品:抢购进度 -->
|
||||
<template>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<view class="progress-title ss-m-r-10"> 已抢{{ percent }}% </view>
|
||||
<view class="progress-box ss-flex ss-col-center">
|
||||
<view class="progerss-active" :style="{ width: percent < 10 ? '10%' : percent + '%' }">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.progress-title {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.progress-box {
|
||||
width: 168rpx;
|
||||
height: 18rpx;
|
||||
background: #f6f6f6;
|
||||
border-radius: 9rpx;
|
||||
}
|
||||
|
||||
.progerss-active {
|
||||
height: 24rpx;
|
||||
background: linear-gradient(86deg, #f60600, #d00500);
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<view
|
||||
class="skeleton-wrap"
|
||||
:class="['theme-' + sys.mode, 'main-' + sys.theme, 'font-' + sys.fontSize]"
|
||||
>
|
||||
<view class="skeleton-banner"></view>
|
||||
<view class="container-box">
|
||||
<view class="container-box-strip title ss-m-b-58"></view>
|
||||
<view class="container-box-strip ss-m-b-20"></view>
|
||||
<view class="container-box-strip ss-m-b-20"></view>
|
||||
<view class="container-box-strip w-364"></view>
|
||||
</view>
|
||||
<view class="container-box">
|
||||
<view class="ss-flex ss-row-between ss-m-b-34">
|
||||
<view class="container-box-strip w-380"></view>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
<view class="ss-flex ss-row-between ss-m-b-34">
|
||||
<view class="container-box-strip w-556"></view>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
<view class="ss-flex ss-row-between">
|
||||
<view class="container-box-strip w-556"></view>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="container-box">
|
||||
<view class="container-box-strip w-198 ss-m-b-42"></view>
|
||||
<view class="ss-flex">
|
||||
<view class="circle ss-m-r-12"></view>
|
||||
<view class="container-box-strip w-252"></view>
|
||||
</view>
|
||||
</view>
|
||||
<su-fixed bottom placeholder bg="bg-white">
|
||||
<view class="ui-tabbar-box">
|
||||
<view class="foot ss-flex ss-col-center">
|
||||
<view class="ss-m-r-54 ss-m-l-32">
|
||||
<view class="rec ss-m-b-8"></view>
|
||||
<view class="oval"></view>
|
||||
</view>
|
||||
<view class="ss-m-r-54">
|
||||
<view class="rec ss-m-b-8"></view>
|
||||
<view class="oval"></view>
|
||||
</view>
|
||||
<view class="ss-m-r-50">
|
||||
<view class="rec ss-m-b-8"></view>
|
||||
<view class="oval"></view>
|
||||
</view>
|
||||
<button class="ss-reset-button add-btn ui-Shadow-Main"></button>
|
||||
<button class="ss-reset-button buy-btn ui-Shadow-Main"></button>
|
||||
</view>
|
||||
</view>
|
||||
</su-fixed>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const sys = computed(() => sheep.$store('sys'));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes loading {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-wrap {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
|
||||
.skeleton-banner {
|
||||
width: 100%;
|
||||
height: calc(100vh - 882rpx);
|
||||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
.container-box {
|
||||
padding: 24rpx 18rpx;
|
||||
margin: 14rpx 20rpx;
|
||||
background: var(--ui-BG);
|
||||
animation: loading 1.4s ease infinite;
|
||||
|
||||
.container-box-strip {
|
||||
height: 40rpx;
|
||||
background: #f3f3f1;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 470rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 25rpx;
|
||||
}
|
||||
|
||||
.w-364 {
|
||||
width: 364rpx;
|
||||
}
|
||||
|
||||
.w-380 {
|
||||
width: 380rpx;
|
||||
}
|
||||
|
||||
.w-556 {
|
||||
width: 556rpx;
|
||||
}
|
||||
|
||||
.w-198 {
|
||||
width: 198rpx;
|
||||
}
|
||||
|
||||
.w-252 {
|
||||
width: 252rpx;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: #f3f3f1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.ui-tabbar-box {
|
||||
box-shadow: 0px -6px 10px 0px rgba(51, 51, 51, 0.2);
|
||||
}
|
||||
|
||||
.foot {
|
||||
height: 100rpx;
|
||||
background: var(--ui-BG);
|
||||
.rec {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
background: #f3f3f1;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.oval {
|
||||
width: 38rpx;
|
||||
height: 22rpx;
|
||||
background: #f3f3f1;
|
||||
border-radius: 11rpx;
|
||||
}
|
||||
.add-btn {
|
||||
width: 214rpx;
|
||||
height: 72rpx;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx 0 0 40rpx;
|
||||
background-color: var(--ui-BG-Main-light);
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
width: 214rpx;
|
||||
height: 72rpx;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
|
||||
border-radius: 0 40rpx 40rpx 0;
|
||||
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,169 @@
|
||||
<!-- 商品详情的底部导航 -->
|
||||
<template>
|
||||
<su-fixed bottom placeholder bg="bg-white">
|
||||
<view class="ui-tabbar-box">
|
||||
<view class="ui-tabbar ss-flex ss-col-center ss-row-between">
|
||||
<view
|
||||
v-if="collectIcon"
|
||||
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
@tap="onFavorite"
|
||||
>
|
||||
<block v-if="modelValue.favorite">
|
||||
<image
|
||||
class="item-icon"
|
||||
:src="sheep.$url.static('/static/img/shop/goods/collect_1.gif')"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="item-title">已收藏</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<image
|
||||
class="item-icon"
|
||||
:src="sheep.$url.static('/static/img/shop/goods/collect_0.png')"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="item-title">收藏</view>
|
||||
</block>
|
||||
</view>
|
||||
<view
|
||||
v-if="serviceIcon"
|
||||
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
@tap="onChat"
|
||||
>
|
||||
<image
|
||||
class="item-icon"
|
||||
:src="sheep.$url.static('/static/img/shop/goods/message.png')"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="item-title">客服</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="shareIcon"
|
||||
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
@tap="showShareModal"
|
||||
>
|
||||
<image
|
||||
class="item-icon"
|
||||
:src="sheep.$url.static('/static/img/shop/goods/share.png')"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="item-title">分享</view>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</su-fixed>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
*
|
||||
* 底部导航
|
||||
*
|
||||
* @property {String} bg - 背景颜色Class
|
||||
* @property {String} ui - 自定义样式Class
|
||||
* @property {Boolean} noFixed - 是否定位
|
||||
* @property {Boolean} topRadius - 上圆角
|
||||
*/
|
||||
import { reactive } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { showShareModal } from '@/sheep/hooks/useModal';
|
||||
import FavoriteApi from '@/sheep/api/product/favorite';
|
||||
|
||||
// 数据
|
||||
const state = reactive({});
|
||||
|
||||
// 接收参数
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
bg: {
|
||||
type: String,
|
||||
default: 'bg-white',
|
||||
},
|
||||
bgStyles: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
ui: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
noFixed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
topRadius: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
collectIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
serviceIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
shareIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
async function onFavorite() {
|
||||
// 情况一:取消收藏
|
||||
if (props.modelValue.favorite) {
|
||||
const { code } = await FavoriteApi.deleteFavorite(props.modelValue.id);
|
||||
if (code !== 0) {
|
||||
return
|
||||
}
|
||||
sheep.$helper.toast('取消收藏');
|
||||
props.modelValue.favorite = false;
|
||||
// 情况二:添加收藏
|
||||
} else {
|
||||
const { code } = await FavoriteApi.createFavorite(props.modelValue.id);
|
||||
if (code !== 0) {
|
||||
return
|
||||
}
|
||||
sheep.$helper.toast('收藏成功');
|
||||
props.modelValue.favorite = true;
|
||||
}
|
||||
}
|
||||
|
||||
const onChat = () => {
|
||||
sheep.$router.go('/pages/chat/index', {
|
||||
id: props.modelValue.id,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ui-tabbar-box {
|
||||
box-shadow: 0px -6px 10px 0px rgba(51, 51, 51, 0.2);
|
||||
}
|
||||
.ui-tabbar {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
|
||||
.detail-tabbar-item {
|
||||
width: 100rpx;
|
||||
|
||||
.item-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
line-height: 20rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,140 @@
|
||||
<!-- 拼团活动参团记录卡片 -->
|
||||
<template>
|
||||
<view v-if="state.list.length > 0" class="groupon-list detail-card ss-p-x-20">
|
||||
<view class="join-activity ss-flex ss-row-between ss-m-t-30">
|
||||
<view class="">已有{{ state.list.length }}人参与活动</view>
|
||||
<text class="cicon-forward"></text>
|
||||
</view>
|
||||
<view
|
||||
v-for="(record, index) in state.list"
|
||||
@tap="sheep.$router.go('/pages/activity/groupon/detail', { id: record.id })"
|
||||
:key="index"
|
||||
class="ss-m-t-40 ss-flex ss-row-between border-bottom ss-p-b-30"
|
||||
>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<image :src="sheep.$url.cdn(record.avatar)" class="user-avatar"></image>
|
||||
<view class="user-nickname ss-m-l-20 ss-line-1">{{ record.nickname }}</view>
|
||||
</view>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<view class="ss-flex-col ss-col-bottom ss-m-r-20">
|
||||
<view class="title ss-flex ss-m-b-14">
|
||||
还差
|
||||
<view class="num">{{ record.userSize - record.userCount }}人</view>
|
||||
成团
|
||||
</view>
|
||||
<view class="end-time">{{ endTime(record.expireTime) }}</view>
|
||||
</view>
|
||||
<view class="">
|
||||
<button class="ss-reset-button go-btn" @tap.stop="onJoinGroupon(record)"> 去参团 </button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { useDurationTime } from '@/sheep/hooks/useGoods';
|
||||
import CombinationApi from '@/sheep/api/promotion/combination';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
});
|
||||
const state = reactive({
|
||||
list: [],
|
||||
});
|
||||
|
||||
// 去参团
|
||||
const emits = defineEmits(['join']);
|
||||
function onJoinGroupon(record) {
|
||||
emits('join', record);
|
||||
}
|
||||
|
||||
// 结束时间或状态
|
||||
function endTime(time) {
|
||||
const durationTime = useDurationTime(time);
|
||||
|
||||
if (durationTime.ms <= 0) {
|
||||
return '该团已解散';
|
||||
}
|
||||
|
||||
let timeText = '剩余 ';
|
||||
timeText += `${durationTime.h}时`;
|
||||
timeText += `${durationTime.m}分`;
|
||||
timeText += `${durationTime.s}秒`;
|
||||
return timeText;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
// 查询参团记录
|
||||
// status = 0 表示未成团
|
||||
const { data } = await CombinationApi.getHeadCombinationRecordList(props.modelValue.id, 0, 10);
|
||||
state.list = data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-card {
|
||||
background-color: $white;
|
||||
margin: 14rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.groupon-list {
|
||||
.join-activity {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
|
||||
.cicon-forward {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: #ececec;
|
||||
border-radius: 60rpx;
|
||||
}
|
||||
|
||||
.user-nickname {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
width: 160rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #666666;
|
||||
|
||||
.num {
|
||||
color: #ff6000;
|
||||
}
|
||||
}
|
||||
|
||||
.end-time {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.go-btn {
|
||||
width: 140rpx;
|
||||
height: 60rpx;
|
||||
background: linear-gradient(90deg, #ff6000 0%, #fe832a 100%);
|
||||
border-radius: 30rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!-- 页面;暂时没用到 -->
|
||||
<template>
|
||||
<view class="list-goods-card ss-flex-col" @tap="onClick">
|
||||
<view class="md-img-box">
|
||||
<image class="goods-img md-img-box" :src="sheep.$url.cdn(img)" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="md-goods-content ss-flex-col ss-row-around">
|
||||
<view class="md-goods-title ss-line-2 ss-m-x-20 ss-m-t-6 ss-m-b-16">{{ title }}</view>
|
||||
<view class="md-goods-subtitle ss-line-1 ss-p-y-10 ss-p-20">{{ subTitle }}</view>
|
||||
<view class="ss-flex ss-col-center ss-row-between ss-m-b-16 ss-m-x-20">
|
||||
<view class="md-goods-price text-price">{{ price }}</view>
|
||||
<view class="goods-origin-price text-price">{{ originPrice }}</view>
|
||||
<view class="sales-text">已售{{ sales }}件</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { computed, reactive } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
img: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
price: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
originPrice: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
sales: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['click']);
|
||||
const onClick = () => {
|
||||
emits('click');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.sales-text {
|
||||
font-size: 20rpx;
|
||||
color: #c4c4c4;
|
||||
}
|
||||
|
||||
.goods-origin-price {
|
||||
font-size: 20rpx;
|
||||
color: #c4c4c4;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.list-goods-card {
|
||||
overflow: hidden;
|
||||
width: 344rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: $white;
|
||||
box-shadow: 0 0 20rpx 4rpx rgba(199, 199, 199, 0.22);
|
||||
border-radius: 20rpx;
|
||||
|
||||
.md-img-box {
|
||||
width: 344rpx;
|
||||
height: 344rpx;
|
||||
}
|
||||
|
||||
.md-goods-title {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
.md-goods-subtitle {
|
||||
background-color: var(--ui-BG-Main-tag);
|
||||
color: var(--ui-BG-Main);
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.md-goods-price {
|
||||
font-size: 30rpx;
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<!-- 页面;暂时没用到 -->
|
||||
<template>
|
||||
<su-fixed
|
||||
alway
|
||||
:bgStyles="{ background: '#fff' }"
|
||||
:val="0"
|
||||
noNav
|
||||
:opacity="false"
|
||||
placeholder
|
||||
index="10090"
|
||||
>
|
||||
<su-status-bar />
|
||||
<view
|
||||
class="ui-bar ss-flex ss-col-center ss-row-between ss-p-x-20"
|
||||
:style="[{ height: sys_navBar - sys_statusBar + 'px' }]"
|
||||
>
|
||||
<!-- 左 -->
|
||||
<view class="left-box">
|
||||
<text
|
||||
class="_icon-back back-icon"
|
||||
@tap="toBack"
|
||||
:style="[{ color: state.iconColor }]"
|
||||
></text>
|
||||
</view>
|
||||
<!-- 中 -->
|
||||
<uni-search-bar
|
||||
class="ss-flex-1"
|
||||
radius="33"
|
||||
:placeholder="placeholder"
|
||||
cancelButton="none"
|
||||
:focus="true"
|
||||
v-model="state.searchVal"
|
||||
@confirm="onSearch"
|
||||
/>
|
||||
<!-- 右 -->
|
||||
<view class="right">
|
||||
<text class="sicon-more" :style="[{ color: state.iconColor }]" @tap="showMenuTools" />
|
||||
</view>
|
||||
<!-- #ifdef MP -->
|
||||
<view :style="[capsuleStyle]"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</su-fixed>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { showMenuTools } from '@/sheep/hooks/useModal';
|
||||
|
||||
const sys_statusBar = sheep.$platform.device.statusBarHeight;
|
||||
const sys_navBar = sheep.$platform.navbar;
|
||||
const capsuleStyle = {
|
||||
width: sheep.$platform.capsule.width + 'px',
|
||||
height: sheep.$platform.capsule.height + 'px',
|
||||
margin: '0 ' + (sheep.$platform.device.windowWidth - sheep.$platform.capsule.right) + 'px',
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
iconColor: '#000',
|
||||
searchVal: '',
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '搜索内容',
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['searchConfirm']);
|
||||
|
||||
// 返回
|
||||
const toBack = () => {
|
||||
sheep.$router.back();
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const onSearch = () => {
|
||||
emits('searchConfirm', state.searchVal);
|
||||
};
|
||||
|
||||
const onTab = (item) => {};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.back-icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
.sicon-more {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,562 @@
|
||||
<!-- 拼团商品详情 -->
|
||||
<template>
|
||||
<s-layout :onShareAppMessage="shareInfo" navbar="goods">
|
||||
<!-- 标题栏 -->
|
||||
<detailNavbar />
|
||||
<!-- 骨架屏 -->
|
||||
<detailSkeleton v-if="state.skeletonLoading" />
|
||||
<!-- 下架/售罄提醒 -->
|
||||
<s-empty
|
||||
v-else-if="
|
||||
state.goodsInfo === null ||
|
||||
state.activity.status !== 0 ||
|
||||
state.activity.endTime < new Date().getTime()
|
||||
"
|
||||
text="活动不存在或已结束"
|
||||
icon="/static/soldout-empty.png"
|
||||
showAction
|
||||
actionText="返回上一页"
|
||||
@clickAction="sheep.$router.back()"
|
||||
/>
|
||||
<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 detail-card ss-m-y-14 ss-m-x-20 ss-p-x-20 ss-p-y-34">
|
||||
<view class="ss-flex ss-row-between ss-m-b-60">
|
||||
<view>
|
||||
<view class="price-box ss-flex ss-col-bottom ss-m-b-18">
|
||||
<view class="price-text ss-m-r-16">
|
||||
{{ fen2yuan(state.activity.price || state.goodsInfo.price) }}
|
||||
</view>
|
||||
<view class="tig ss-flex ss-col-center">
|
||||
<view class="tig-icon ss-flex ss-col-center ss-row-center">
|
||||
<view class="groupon-tag">
|
||||
<image
|
||||
:src="sheep.$url.static('/static/img/shop/goods/groupon-tag.png')"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tig-title">拼团价</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="ss-flex ss-row-between">
|
||||
<view class="origin-price ss-flex ss-col-center" v-if="state.goodsInfo.price">
|
||||
单买价:
|
||||
<view class="origin-price-text">
|
||||
{{ fen2yuan(state.goodsInfo.marketPrice) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="countdown-box" v-if="endTime.ms > 0">
|
||||
<view class="countdown-title ss-m-b-20">距结束仅剩</view>
|
||||
<view class="ss-flex countdown-time">
|
||||
<view class="ss-flex countdown-h">{{ endTime.h }}</view>
|
||||
<view class="ss-m-x-4">:</view>
|
||||
<view class="countdown-num ss-flex ss-row-center">{{ endTime.m }}</view>
|
||||
<view class="ss-m-x-4">:</view>
|
||||
<view class="countdown-num ss-flex ss-row-center">{{ endTime.s }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="countdown-title" v-else> 活动已结束 </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>
|
||||
|
||||
<!-- 参团列表 -->
|
||||
<groupon-card-list v-model="state.activity" @join="onJoinGroupon" />
|
||||
|
||||
<!-- 规格与数量弹框 -->
|
||||
<s-select-groupon-sku
|
||||
:show="state.showSelectSku"
|
||||
:goodsInfo="state.goodsInfo"
|
||||
:grouponAction="state.grouponAction"
|
||||
:grouponNum="state.grouponNum"
|
||||
@buy="onBuy"
|
||||
@change="onSkuChange"
|
||||
@close="onSkuClose"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 评价 -->
|
||||
<detail-comment-card class="detail-comment-selector" :goodsId="state.goodsId" />
|
||||
<!-- 详情 -->
|
||||
<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"
|
||||
@tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })"
|
||||
>
|
||||
<view class="btn-price">{{ fen2yuan(state.goodsInfo.marketPrice) }}</view>
|
||||
<view>原价购买</view>
|
||||
</button>
|
||||
<button
|
||||
class="ss-reset-button btn-tox ss-flex-col"
|
||||
@tap="onCreateGroupon"
|
||||
:class="
|
||||
state.activity.status === 0 && state.goodsInfo.stock !== 0
|
||||
? 'check-btn-box'
|
||||
: 'disabled-btn-box'
|
||||
"
|
||||
:disabled="state.goodsInfo.stock === 0 || state.activity.status !== 0"
|
||||
>
|
||||
<view class="btn-price">{{
|
||||
fen2yuan(
|
||||
state.selectedSku.price * state.selectedSku.count ||
|
||||
state.activity.price * state.selectedSku.count ||
|
||||
state.goodsInfo.price * state.selectedSku.count ||
|
||||
state.goodsInfo.price,
|
||||
)
|
||||
}}</view>
|
||||
<view v-if="state.activity.startTime > new Date().getTime()">未开始</view>
|
||||
<view v-else-if="state.activity.endTime <= new Date().getTime()">已结束</view>
|
||||
<view v-else>
|
||||
<view v-if="state.goodsInfo.stock === 0">已售罄</view>
|
||||
<view v-else>立即开团</view>
|
||||
</view>
|
||||
</button>
|
||||
</view>
|
||||
</detail-tabbar>
|
||||
</block>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed } from 'vue';
|
||||
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
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 grouponCardList from './components/groupon/groupon-card-list.vue';
|
||||
import { useDurationTime, formatGoodsSwiper, fen2yuan } from '@/sheep/hooks/useGoods';
|
||||
import CombinationApi from '@/sheep/api/promotion/combination';
|
||||
import SpuApi from '@/sheep/api/product/spu';
|
||||
import { SharePageEnum } from '@/sheep/helper/const';
|
||||
|
||||
const headerBg = sheep.$url.css('/static/img/shop/goods/groupon-bg.png');
|
||||
const btnBg = sheep.$url.css('/static/img/shop/goods/groupon-btn.png');
|
||||
const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png');
|
||||
const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
|
||||
|
||||
onPageScroll(() => {});
|
||||
const state = reactive({
|
||||
skeletonLoading: true, // 骨架屏
|
||||
goodsId: 0, // 商品ID
|
||||
goodsInfo: {}, // 商品信息
|
||||
goodsSwiper: [], // 商品轮播图
|
||||
showSelectSku: false, // 显示规格弹框
|
||||
selectedSku: {}, // 选中的规格属性
|
||||
activity: {}, // 团购活动
|
||||
grouponId: 0, // 团购ID
|
||||
grouponNum: 0, // 团购人数
|
||||
grouponAction: 'create', // 团购操作
|
||||
combinationHeadId: null, // 拼团团长编号
|
||||
});
|
||||
|
||||
// 倒计时
|
||||
const endTime = computed(() => {
|
||||
return useDurationTime(state.activity.endTime);
|
||||
});
|
||||
|
||||
// 规格变更
|
||||
function onSkuChange(e) {
|
||||
state.selectedSku = e;
|
||||
}
|
||||
|
||||
function onSkuClose() {
|
||||
state.showSelectSku = false;
|
||||
}
|
||||
|
||||
// 发起拼团
|
||||
function onCreateGroupon() {
|
||||
state.grouponAction = 'create';
|
||||
state.grouponId = 0;
|
||||
state.showSelectSku = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去参团
|
||||
*
|
||||
* @param record 团长的团购记录
|
||||
*/
|
||||
function onJoinGroupon(record) {
|
||||
state.grouponAction = 'join';
|
||||
state.grouponId = record.activityId;
|
||||
state.combinationHeadId = record.id;
|
||||
state.grouponNum = record.userSize;
|
||||
state.showSelectSku = true;
|
||||
}
|
||||
|
||||
// 立即购买
|
||||
function onBuy(sku) {
|
||||
sheep.$router.go('/pages/order/confirm', {
|
||||
data: JSON.stringify({
|
||||
order_type: 'goods',
|
||||
combinationActivityId: state.activity.id,
|
||||
combinationHeadId: state.combinationHeadId,
|
||||
items: [
|
||||
{
|
||||
skuId: sku.id,
|
||||
count: sku.count,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// 分享信息
|
||||
const shareInfo = computed(() => {
|
||||
if (isEmpty(state.activity)) return {};
|
||||
return sheep.$platform.share.getShareInfo(
|
||||
{
|
||||
title: state.activity.name,
|
||||
image: sheep.$url.cdn(state.goodsInfo.picUrl),
|
||||
params: {
|
||||
page: SharePageEnum.GROUPON.value,
|
||||
query: state.activity.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'goods', // 商品海报
|
||||
title: state.activity.name, // 商品标题
|
||||
image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
|
||||
price: fen2yuan(state.goodsInfo.price), // 商品价格
|
||||
marketPrice: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
onLoad(async (options) => {
|
||||
// 非法参数
|
||||
if (!options.id) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
state.grouponId = options.id;
|
||||
// 加载活动信息
|
||||
const { code, data: activity } = await CombinationApi.getCombinationActivity(state.grouponId);
|
||||
if (code !== 0) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
state.activity = activity;
|
||||
// 加载商品信息
|
||||
const { data: spu } = await SpuApi.getSpuDetail(activity.spuId);
|
||||
if (code !== 0) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
state.goodsId = spu.id;
|
||||
|
||||
// 默认显示最低价
|
||||
spu.price = activity.products.reduce((min, product) => {
|
||||
return Math.min(min, product.combinationPrice || Infinity);
|
||||
}, Infinity);
|
||||
|
||||
// 价格、库存使用活动的
|
||||
spu.skus.forEach((sku) => {
|
||||
const product = activity.products.find((product) => product.skuId === sku.id);
|
||||
if (product) {
|
||||
sku.price = product.combinationPrice;
|
||||
} else {
|
||||
// 找不到可能是没配置,则不能发起秒杀
|
||||
sku.stock = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭骨架屏
|
||||
state.skeletonLoading = false;
|
||||
if (code === 0) {
|
||||
state.goodsInfo = spu;
|
||||
state.grouponNum = activity.userSize;
|
||||
state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls);
|
||||
} else {
|
||||
// 未找到商品
|
||||
state.goodsInfo = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.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;
|
||||
|
||||
&::before {
|
||||
content: '¥';
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
margin-left: -2rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 4rpx 0 0 4rpx;
|
||||
|
||||
.groupon-tag {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tig-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
color: #ffffff;
|
||||
width: 86rpx;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
.disabled-btn-box[disabled] {
|
||||
background-color: transparent;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
.btn-title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
.btn-price {
|
||||
font-family: OPPOSANS;
|
||||
|
||||
&::before {
|
||||
content: '¥';
|
||||
}
|
||||
}
|
||||
.more-item-box {
|
||||
.more-item {
|
||||
width: 156rpx;
|
||||
height: 58rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
.more-item-hover {
|
||||
background: rgba(#ffefe5, 0.32);
|
||||
color: #ff6000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -0,0 +1,672 @@
|
||||
<template>
|
||||
<view>
|
||||
<s-layout :onShareAppMessage="shareInfo" navbar="goods">
|
||||
<!-- 标题栏 -->
|
||||
<detailNavbar />
|
||||
|
||||
<!-- 骨架屏 -->
|
||||
<detailSkeleton v-if="state.skeletonLoading" />
|
||||
<!-- 下架/售罄提醒 -->
|
||||
<s-empty
|
||||
v-else-if="state.goodsInfo === null"
|
||||
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="formatGoodsSwiper(state.goodsInfo.sliderPicUrls)"
|
||||
otStyle="tag"
|
||||
imageMode="widthFix"
|
||||
dotCur="bg-mask-40"
|
||||
: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">
|
||||
距结束仅剩
|
||||
<s-count-down
|
||||
: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-30 ss-p-x-20">
|
||||
<!-- 没有限时折扣/会员价的优惠信息时,展示的价格信息 -->
|
||||
<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-text ss-m-r-16">
|
||||
{{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
|
||||
</view>
|
||||
<view
|
||||
class="origin-price-text"
|
||||
v-if="state.goodsInfo.marketPrice > state.goodsInfo.price"
|
||||
>
|
||||
{{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="sales-text">
|
||||
{{ formatSales('exact', state.goodsInfo.salesCount) }}
|
||||
</view>
|
||||
</view>
|
||||
<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">
|
||||
<view class="tag-box ss-flex">
|
||||
<!-- 最多打印 3 条,所以需要扣除优惠劵已打印的 -->
|
||||
<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"
|
||||
@tap="onOpenActivity"
|
||||
>
|
||||
<text>{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</div>
|
||||
<!-- 领取优惠劵的按钮 -->
|
||||
<view
|
||||
class="get-coupon-box ss-flex ss-col-center ss-m-l-20"
|
||||
@tap="onOpenActivity"
|
||||
v-if="state.couponInfo.length"
|
||||
>
|
||||
<view class="discounts-title ss-m-r-8">领券</view>
|
||||
<text class="cicon-forward"></text>
|
||||
</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
|
||||
v-model="state.selectedSku.goods_sku_text"
|
||||
:sku="state.selectedSku"
|
||||
@tap="state.showSelectSku = true"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 规格与数量弹框 -->
|
||||
<s-select-sku
|
||||
:goodsInfo="state.goodsInfo"
|
||||
:show="state.showSelectSku"
|
||||
@addCart="onAddCart"
|
||||
@buy="onBuy"
|
||||
@change="onSkuChange"
|
||||
@close="state.showSelectSku = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 评价 -->
|
||||
<detail-comment-card class="detail-comment-selector" :goodsId="state.goodsId" />
|
||||
<!-- 详情 -->
|
||||
<detail-content-card
|
||||
class="detail-content-selector"
|
||||
:content="state.goodsInfo.description"
|
||||
/>
|
||||
|
||||
<!-- 活动跳转:拼团/秒杀/砍价活动 -->
|
||||
<detail-activity-tip
|
||||
v-if="state.activityList.length > 0"
|
||||
:activity-list="state.activityList"
|
||||
/>
|
||||
|
||||
<!-- 详情 tabbar -->
|
||||
<detail-tabbar v-model="state.goodsInfo">
|
||||
<view class="buy-box ss-flex ss-col-center ss-p-r-20" v-if="state.goodsInfo.stock > 0">
|
||||
<button
|
||||
class="ss-reset-button add-btn ui-Shadow-Main"
|
||||
@tap="state.showSelectSku = true"
|
||||
>
|
||||
加入购物车
|
||||
</button>
|
||||
<button
|
||||
class="ss-reset-button buy-btn ui-Shadow-Main"
|
||||
@tap="state.showSelectSku = true"
|
||||
>
|
||||
立即购买
|
||||
</button>
|
||||
</view>
|
||||
<view class="buy-box ss-flex ss-col-center ss-p-r-20" v-else>
|
||||
<button class="ss-reset-button disabled-btn" disabled> 已售罄 </button>
|
||||
</view>
|
||||
</detail-tabbar>
|
||||
|
||||
<!-- 满减送/限时折扣活动弹窗 -->
|
||||
<s-activity-pop
|
||||
v-model="state"
|
||||
:show="state.showActivityModel"
|
||||
@close="state.showActivityModel = false"
|
||||
@get="onTakeCoupon"
|
||||
/>
|
||||
</block>
|
||||
</s-layout>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed } from 'vue';
|
||||
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import CouponApi from '@/sheep/api/promotion/coupon';
|
||||
import ActivityApi from '@/sheep/api/promotion/activity';
|
||||
import FavoriteApi from '@/sheep/api/product/favorite';
|
||||
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 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 detailActivityTip from './components/detail/detail-activity-tip.vue';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import SpuApi from '@/sheep/api/product/spu';
|
||||
|
||||
onPageScroll(() => {});
|
||||
import OrderApi from '@/sheep/api/trade/order';
|
||||
import { SharePageEnum } from '@/sheep/helper/const';
|
||||
|
||||
const bgColor = {
|
||||
bgColor: '#E93323',
|
||||
Color: '#fff',
|
||||
width: '44rpx',
|
||||
timeTxtwidth: '16rpx',
|
||||
isDay: true,
|
||||
};
|
||||
const isLogin = computed(() => sheep.$store('user').isLogin);
|
||||
const state = reactive({
|
||||
goodsId: 0,
|
||||
skeletonLoading: true, // SPU 加载中
|
||||
goodsInfo: {}, // SPU 信息
|
||||
showSelectSku: false, // 是否展示 SKU 选择弹窗
|
||||
selectedSku: {}, // 选中的 SKU
|
||||
settlementSku: {}, // 结算的 SKU:由于 selectedSku 不进行默认选中,所以初始使用结算价格最低的 SKU 作为基础展示
|
||||
showModel: false, // 是否展示 Coupon 优惠劵的弹窗
|
||||
couponInfo: [], // 可领取的 Coupon 优惠劵的列表
|
||||
showActivityModel: false, // 【满减送/限时折扣】是否展示 Activity 营销活动的弹窗
|
||||
rewardActivity: {}, // 【满减送】活动
|
||||
activityList: [], // 【秒杀/拼团/砍价】可参与的 Activity 营销活动的列表
|
||||
});
|
||||
|
||||
// 规格变更
|
||||
function onSkuChange(e) {
|
||||
state.selectedSku = e;
|
||||
state.settlementSku = e;
|
||||
}
|
||||
|
||||
// 添加购物车
|
||||
function onAddCart(e) {
|
||||
if (!e.id) {
|
||||
sheep.$helper.toast('请选择商品规格');
|
||||
return;
|
||||
}
|
||||
sheep.$store('cart').add(e);
|
||||
}
|
||||
|
||||
// 立即购买
|
||||
function onBuy(e) {
|
||||
if (!e.id) {
|
||||
sheep.$helper.toast('请选择商品规格');
|
||||
return;
|
||||
}
|
||||
sheep.$router.go('/pages/order/confirm', {
|
||||
data: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
skuId: e.id,
|
||||
count: e.goods_num,
|
||||
categoryId: state.goodsInfo.categoryId,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// 打开营销弹窗
|
||||
function onOpenActivity() {
|
||||
state.showActivityModel = true;
|
||||
}
|
||||
|
||||
// 立即领取优惠劵
|
||||
async function onTakeCoupon(id) {
|
||||
const { code } = await CouponApi.takeCoupon(id);
|
||||
if (code !== 0) {
|
||||
return;
|
||||
}
|
||||
uni.showToast({
|
||||
title: '领取成功',
|
||||
});
|
||||
setTimeout(() => {
|
||||
getCoupon();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const shareInfo = computed(() => {
|
||||
if (isEmpty(state.goodsInfo)) return {};
|
||||
return sheep.$platform.share.getShareInfo(
|
||||
{
|
||||
title: state.goodsInfo.name,
|
||||
image: sheep.$url.cdn(state.goodsInfo.picUrl),
|
||||
desc: state.goodsInfo.introduction,
|
||||
params: {
|
||||
page: SharePageEnum.GOODS.value,
|
||||
query: state.goodsInfo.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'goods', // 商品海报
|
||||
title: state.goodsInfo.name, // 商品名称
|
||||
image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
|
||||
price: fen2yuan(state.goodsInfo.price), // 商品价格
|
||||
original_price: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
async function getCoupon() {
|
||||
const { code, data } = await CouponApi.getCouponTemplateList(state.goodsId, 2, 10);
|
||||
if (code === 0) {
|
||||
state.couponInfo = data;
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
// 非法参数
|
||||
if (!options.id) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
state.goodsId = options.id;
|
||||
// 1. 加载商品信息
|
||||
SpuApi.getSpuDetail(state.goodsId).then((res) => {
|
||||
if (res.code !== 0 || !res.data) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
// 加载到商品
|
||||
state.skeletonLoading = false;
|
||||
state.goodsInfo = res.data;
|
||||
// 获取结算信息
|
||||
getSettlementByIds(state.goodsId);
|
||||
// 加载是否收藏
|
||||
if (isLogin.value) {
|
||||
FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => {
|
||||
if (res.code !== 0) {
|
||||
return;
|
||||
}
|
||||
state.goodsInfo.favorite = res.data;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 加载优惠劵信息
|
||||
getCoupon();
|
||||
|
||||
// 3. 加载营销活动信息
|
||||
ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
return;
|
||||
}
|
||||
state.activityList = res.data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-card {
|
||||
background-color: #ffff;
|
||||
margin: 14rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 价格标题卡片
|
||||
.title-card {
|
||||
.price-box {
|
||||
.price-text {
|
||||
font-size: 42rpx;
|
||||
font-weight: 500;
|
||||
color: #ff3000;
|
||||
line-height: 30rpx;
|
||||
font-family: OPPOSANS;
|
||||
|
||||
&::before {
|
||||
content: '¥';
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.origin-price-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
text-decoration: line-through;
|
||||
color: $gray-c;
|
||||
font-family: OPPOSANS;
|
||||
|
||||
&::before {
|
||||
content: '¥';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sales-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: $gray-c;
|
||||
}
|
||||
|
||||
.discounts-box {
|
||||
.tag-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag-box {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tag {
|
||||
flex-shrink: 0;
|
||||
padding: 4rpx 10rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 4rpx;
|
||||
color: var(--ui-BG-Main);
|
||||
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;
|
||||
}
|
||||
|
||||
.subtitle-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: $dark-9;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 购买
|
||||
.buy-box {
|
||||
.add-btn {
|
||||
width: 214rpx;
|
||||
height: 72rpx;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx 0 0 40rpx;
|
||||
background-color: var(--ui-BG-Main-light);
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
width: 214rpx;
|
||||
height: 72rpx;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
|
||||
border-radius: 0 40rpx 40rpx 0;
|
||||
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.disabled-btn {
|
||||
width: 428rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 40rpx;
|
||||
background: #999999;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.model-box {
|
||||
height: 60vh;
|
||||
|
||||
.model-content {
|
||||
height: 56vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
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>
|
||||
@@ -0,0 +1,407 @@
|
||||
<template>
|
||||
<s-layout
|
||||
navbar="normal"
|
||||
:leftWidth="0"
|
||||
:rightWidth="0"
|
||||
tools="search"
|
||||
:defaultSearch="state.keyword"
|
||||
@search="onSearch"
|
||||
>
|
||||
<!-- 筛选 -->
|
||||
<su-sticky bgColor="#fff">
|
||||
<view class="ss-flex">
|
||||
<view class="ss-flex-1">
|
||||
<su-tabs
|
||||
:list="state.tabList"
|
||||
:scrollable="false"
|
||||
@change="onTabsChange"
|
||||
:current="state.currentTab"
|
||||
/>
|
||||
</view>
|
||||
<view class="list-icon" @tap="state.iconStatus = !state.iconStatus">
|
||||
<text v-if="state.iconStatus" class="sicon-goods-list" />
|
||||
<text v-else class="sicon-goods-card" />
|
||||
</view>
|
||||
</view>
|
||||
</su-sticky>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<su-popup
|
||||
:show="state.showFilter"
|
||||
type="top"
|
||||
round="10"
|
||||
:space="sys_navBar + 38"
|
||||
backgroundColor="#F6F6F6"
|
||||
:zIndex="10"
|
||||
@close="state.showFilter = false"
|
||||
>
|
||||
<view class="filter-list-box">
|
||||
<view
|
||||
class="filter-item"
|
||||
v-for="(item, index) in state.tabList[state.currentTab].list"
|
||||
:key="item.value"
|
||||
:class="[{ 'filter-item-active': index === state.curFilter }]"
|
||||
@tap="onFilterItem(index)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</su-popup>
|
||||
|
||||
<!-- 情况一:单列布局 -->
|
||||
<view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20">
|
||||
<view
|
||||
class="ss-p-l-20 ss-p-r-20 ss-m-b-20"
|
||||
v-for="item in state.pagination.list"
|
||||
:key="item.id"
|
||||
>
|
||||
<s-goods-column
|
||||
class=""
|
||||
size="lg"
|
||||
:data="item"
|
||||
:topRadius="10"
|
||||
:bottomRadius="10"
|
||||
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 情况二:双列布局 -->
|
||||
<view
|
||||
v-if="!state.iconStatus && state.pagination.total > 0"
|
||||
class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top"
|
||||
>
|
||||
<view class="goods-list-box">
|
||||
<view class="left-list" v-for="item in state.leftGoodsList" :key="item.id">
|
||||
<s-goods-column
|
||||
class="goods-md-box"
|
||||
size="md"
|
||||
:data="item"
|
||||
:topRadius="10"
|
||||
:bottomRadius="10"
|
||||
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
|
||||
@getHeight="mountMasonry($event, 'left')"
|
||||
>
|
||||
<template v-slot:cart>
|
||||
<button class="ss-reset-button cart-btn" />
|
||||
</template>
|
||||
</s-goods-column>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-list-box">
|
||||
<view class="right-list" v-for="item in state.rightGoodsList" :key="item.id">
|
||||
<s-goods-column
|
||||
class="goods-md-box"
|
||||
size="md"
|
||||
:topRadius="10"
|
||||
:bottomRadius="10"
|
||||
:data="item"
|
||||
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
|
||||
@getHeight="mountMasonry($event, 'right')"
|
||||
>
|
||||
<template v-slot:cart>
|
||||
<button class="ss-reset-button cart-btn" />
|
||||
</template>
|
||||
</s-goods-column>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<uni-load-more
|
||||
v-if="state.pagination.total > 0"
|
||||
:status="state.loadStatus"
|
||||
:content-text="{
|
||||
contentdown: '上拉加载更多',
|
||||
}"
|
||||
@tap="loadMore"
|
||||
/>
|
||||
<s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" />
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
|
||||
import sheep from '@/sheep';
|
||||
import _ from 'lodash-es';
|
||||
import { resetPagination } from '@/sheep/helper/utils';
|
||||
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 emits = defineEmits(['close', 'change']);
|
||||
|
||||
const state = reactive({
|
||||
pagination: {
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNo: 1,
|
||||
pageSize: 6,
|
||||
},
|
||||
currentSort: undefined,
|
||||
currentOrder: undefined,
|
||||
currentTab: 0, // 当前选中的 tab
|
||||
curFilter: 0, // 当前选中的 list 筛选项
|
||||
showFilter: false,
|
||||
iconStatus: false, // true - 单列布局;false - 双列布局
|
||||
keyword: '',
|
||||
categoryId: 0,
|
||||
tabList: [
|
||||
{
|
||||
name: '综合推荐',
|
||||
list: [
|
||||
{
|
||||
label: '综合推荐',
|
||||
},
|
||||
{
|
||||
label: '价格升序',
|
||||
sort: 'price',
|
||||
order: true,
|
||||
},
|
||||
{
|
||||
label: '价格降序',
|
||||
sort: 'price',
|
||||
order: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '销量',
|
||||
sort: 'salesCount',
|
||||
order: false,
|
||||
},
|
||||
{
|
||||
name: '新品优先',
|
||||
value: 'createTime',
|
||||
order: false,
|
||||
},
|
||||
],
|
||||
loadStatus: '',
|
||||
leftGoodsList: [], // 双列布局 - 左侧商品
|
||||
rightGoodsList: [], // 双列布局 - 右侧商品
|
||||
});
|
||||
|
||||
// 加载瀑布流
|
||||
let count = 0;
|
||||
let leftHeight = 0;
|
||||
let rightHeight = 0;
|
||||
|
||||
// 处理双列布局 leftGoodsList + rightGoodsList
|
||||
function mountMasonry(height = 0, where = 'left') {
|
||||
if (where === 'left') {
|
||||
leftHeight += height;
|
||||
} else {
|
||||
rightHeight += height;
|
||||
}
|
||||
if (!state.pagination.list[count]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (leftHeight <= rightHeight) {
|
||||
state.leftGoodsList.push(state.pagination.list[count]);
|
||||
} else {
|
||||
state.rightGoodsList.push(state.pagination.list[count]);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
// 清空列表
|
||||
function emptyList() {
|
||||
resetPagination(state.pagination);
|
||||
state.leftGoodsList = [];
|
||||
state.rightGoodsList = [];
|
||||
count = 0;
|
||||
leftHeight = 0;
|
||||
rightHeight = 0;
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function onSearch(e) {
|
||||
state.keyword = e;
|
||||
emptyList();
|
||||
getList(state.currentSort, state.currentOrder);
|
||||
}
|
||||
|
||||
// 点击
|
||||
function onTabsChange(e) {
|
||||
// 如果点击的是【综合推荐】,则直接展开或者收起筛选项
|
||||
if (state.tabList[e.index].list) {
|
||||
state.currentTab = e.index;
|
||||
state.showFilter = !state.showFilter;
|
||||
return;
|
||||
}
|
||||
state.showFilter = false;
|
||||
|
||||
// 如果点击的是【销量】或者【新品优先】,则直接切换 tab
|
||||
if (e.index === state.currentTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.currentTab = e.index;
|
||||
state.currentSort = e.sort;
|
||||
state.currentOrder = e.order;
|
||||
emptyList();
|
||||
getList(e.sort, e.order);
|
||||
}
|
||||
|
||||
// 点击 tab 的 list 筛选项
|
||||
const onFilterItem = (val) => {
|
||||
// 如果点击的是当前的筛选项,则直接收起筛选项,不要加载数据
|
||||
// 这里选择 tabList[0] 的原因,是目前只有它有 list
|
||||
if (
|
||||
state.currentSort === state.tabList[0].list[val].sort &&
|
||||
state.currentOrder === state.tabList[0].list[val].order
|
||||
) {
|
||||
state.showFilter = false;
|
||||
return;
|
||||
}
|
||||
state.showFilter = false;
|
||||
|
||||
// 设置筛选条件
|
||||
state.curFilter = val;
|
||||
state.tabList[0].name = state.tabList[0].list[val].label;
|
||||
state.currentSort = state.tabList[0].list[val].sort;
|
||||
state.currentOrder = state.tabList[0].list[val].order;
|
||||
// 清空 + 加载数据
|
||||
emptyList();
|
||||
getList();
|
||||
};
|
||||
|
||||
async function getList() {
|
||||
state.loadStatus = 'loading';
|
||||
const { code, data } = await SpuApi.getSpuPage({
|
||||
pageNo: state.pagination.pageNo,
|
||||
pageSize: state.pagination.pageSize,
|
||||
sortField: state.currentSort,
|
||||
sortAsc: state.currentOrder,
|
||||
categoryId: state.categoryId,
|
||||
keyword: state.keyword,
|
||||
});
|
||||
if (code !== 0) {
|
||||
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.total = data.total;
|
||||
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
|
||||
mountMasonry();
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
function loadMore() {
|
||||
if (state.loadStatus === 'noMore') {
|
||||
return;
|
||||
}
|
||||
state.pagination.pageNo++;
|
||||
getList(state.currentSort, state.currentOrder);
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
state.categoryId = options.categoryId;
|
||||
state.keyword = options.keyword;
|
||||
getList(state.currentSort, state.currentOrder);
|
||||
});
|
||||
|
||||
// 上拉加载更多
|
||||
onReachBottom(() => {
|
||||
loadMore();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-list-box {
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.left-list {
|
||||
margin-right: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.right-list {
|
||||
margin-left: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-box {
|
||||
&:nth-last-of-type(1) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
&:nth-child(2n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list-icon {
|
||||
width: 80rpx;
|
||||
|
||||
.sicon-goods-card {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.sicon-goods-list {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-card {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.list-filter-tabs {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.filter-list-box {
|
||||
padding: 28rpx 52rpx;
|
||||
|
||||
.filter-item {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
line-height: normal;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
&:nth-last-child(1) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-item-active {
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
height: 50px;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
|
||||
.tab-title {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.cur-tab-title {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 10rpx;
|
||||
background-color: var(--ui-BG-Main);
|
||||
z-index: 12;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,483 @@
|
||||
<!-- 秒杀商品详情 -->
|
||||
<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 detail-card ss-p-y-40 ss-p-x-20">
|
||||
<view class="ss-flex ss-row-between ss-col-center ss-m-b-18">
|
||||
<view class="price-box ss-flex ss-col-bottom">
|
||||
<image
|
||||
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
|
||||
class="point-img"
|
||||
></image>
|
||||
<text class="point-text ss-m-r-16">
|
||||
{{ getShowPrice.point }}
|
||||
{{
|
||||
!getShowPrice.price || getShowPrice.price === 0 ? '' : `+¥${getShowPrice.price}`
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="sales-text">
|
||||
{{ formatExchange(state.goodsInfo.sales_show_type, state.goodsInfo.sales) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="origin-price-text ss-m-b-60" v-if="state.goodsInfo.marketPrice">
|
||||
原价:¥{{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
|
||||
</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-box ss-flex">
|
||||
<image
|
||||
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
|
||||
style="width: 36rpx; height: 36rpx; margin: 0 4rpx"
|
||||
></image>
|
||||
<text class="point-text ss-m-r-16">
|
||||
{{ getShowPrice.point }}
|
||||
{{
|
||||
!getShowPrice.price || getShowPrice.price === 0 ? '' : `+¥${getShowPrice.price}`
|
||||
}}
|
||||
</text>
|
||||
</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, formatExchange, 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, SharePageEnum } from '@/sheep/helper/const';
|
||||
import PointApi from '@/sheep/api/promotion/point';
|
||||
|
||||
const headerBg = sheep.$url.css('/static/img/shop/goods/score-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,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// 分享信息
|
||||
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: SharePageEnum.POINT.value,
|
||||
query: activity.value.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'goods', // 商品海报
|
||||
title: activity.value.name, // 商品标题
|
||||
image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
|
||||
price: (getShowPrice.value.price || 0) + ` + ${getShowPrice.value.point} 积分`, // 积分价格
|
||||
marketPrice: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const activity = ref();
|
||||
|
||||
const getShowPrice = computed(() => {
|
||||
if (!isEmpty(state.selectedSku)) {
|
||||
const sku = state.selectedSku;
|
||||
return {
|
||||
point: sku.point,
|
||||
price: !sku.pointPrice ? '' : fen2yuan(sku.pointPrice),
|
||||
};
|
||||
}
|
||||
return {
|
||||
point: activity.value.point,
|
||||
price: !activity.value.price ? '' : fen2yuan(activity.value.price),
|
||||
};
|
||||
});
|
||||
|
||||
// 查询活动
|
||||
const getActivity = async (id) => {
|
||||
const { data } = await PointApi.getPointActivity(id);
|
||||
if (!data) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
activity.value = data;
|
||||
// 查询商品
|
||||
await getSpu(data.spuId);
|
||||
};
|
||||
|
||||
// 查询商品
|
||||
const getSpu = async (id) => {
|
||||
const { data } = await SpuApi.getSpuDetail(id);
|
||||
if (!data) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
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;
|
||||
state.skeletonLoading = false;
|
||||
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;
|
||||
background-size: 100% 100%;
|
||||
border-radius: 10rpx;
|
||||
background-image: v-bind(headerBg);
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.price-box {
|
||||
.point-img {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
|
||||
.point-text {
|
||||
font-size: 42rpx;
|
||||
font-weight: 500;
|
||||
color: #ff3000;
|
||||
line-height: 36rpx;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
font-size: 42rpx;
|
||||
font-weight: 500;
|
||||
color: #ff3000;
|
||||
line-height: 36rpx;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
}
|
||||
|
||||
.origin-price-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
text-decoration: line-through;
|
||||
color: $gray-c;
|
||||
font-family: OPPOSANS;
|
||||
}
|
||||
|
||||
.sales-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: $gray-c;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.subtitle-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: $dark-9;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 购买
|
||||
.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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,575 @@
|
||||
<!-- 秒杀商品详情 -->
|
||||
<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 !== 'seckill' || endTime.ms <= 0
|
||||
"
|
||||
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">
|
||||
{{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
|
||||
</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 class="countdown-box" v-if="endTime.ms > 0">
|
||||
<view class="countdown-title ss-m-b-20">距结束仅剩</view>
|
||||
<view class="ss-flex countdown-time">
|
||||
<view class="ss-flex countdown-h">{{ endTime.h }}</view>
|
||||
<view class="ss-m-x-4">:</view>
|
||||
<view class="countdown-num ss-flex ss-row-center">{{ endTime.m }}</view>
|
||||
<view class="ss-m-x-4">:</view>
|
||||
<view class="countdown-num ss-flex ss-row-center">{{ endTime.s }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="countdown-title" v-else> 活动已结束 </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>
|
||||
<detail-progress :percent="state.percent" />
|
||||
</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">
|
||||
<!-- TODO: 缺货中 已售罄 判断 设计-->
|
||||
<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 v-else class="ss-reset-button origin-price-btn ss-flex-col">
|
||||
<view
|
||||
class="no-original"
|
||||
:class="
|
||||
state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : ''
|
||||
"
|
||||
>
|
||||
秒杀价
|
||||
</view>
|
||||
</button>
|
||||
<button
|
||||
class="ss-reset-button btn-box ss-flex-col"
|
||||
@tap="state.showSelectSku = true"
|
||||
:class="
|
||||
timeStatusEnum === TimeStatusEnum.STARTED && state.goodsInfo.stock != 0
|
||||
? 'check-btn-box'
|
||||
: 'disabled-btn-box'
|
||||
"
|
||||
:disabled="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED"
|
||||
>
|
||||
<view class="btn-price">{{ fen2yuan(state.goodsInfo.price) }}</view>
|
||||
<view v-if="timeStatusEnum === TimeStatusEnum.STARTED">
|
||||
<view v-if="state.goodsInfo.stock === 0">已售罄</view>
|
||||
<view v-else>立即秒杀</view>
|
||||
</view>
|
||||
<view v-else>{{ timeStatusEnum }}</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, min } from 'lodash-es';
|
||||
import { fen2yuan, formatGoodsSwiper, useDurationTime } 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 detailProgress from './components/detail/detail-progress.vue';
|
||||
import SeckillApi from '@/sheep/api/promotion/seckill';
|
||||
import SpuApi from '@/sheep/api/product/spu';
|
||||
import { getTimeStatusEnum, SharePageEnum, TimeStatusEnum } from '@/sheep/helper/const';
|
||||
|
||||
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,
|
||||
percent: 0,
|
||||
price: '',
|
||||
});
|
||||
|
||||
const endTime = computed(() => {
|
||||
return useDurationTime(activity.value.endTime);
|
||||
});
|
||||
|
||||
// 规格变更
|
||||
function onSkuChange(e) {
|
||||
state.selectedSku = e;
|
||||
}
|
||||
|
||||
// 立即购买
|
||||
function onBuy(sku) {
|
||||
sheep.$router.go('/pages/order/confirm', {
|
||||
data: JSON.stringify({
|
||||
order_type: 'goods',
|
||||
buy_type: 'seckill',
|
||||
seckillActivityId: activity.value.id,
|
||||
items: [
|
||||
{
|
||||
skuId: sku.id,
|
||||
count: sku.count,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// 分享信息
|
||||
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: SharePageEnum.SECKILL.value,
|
||||
query: activity.value.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'goods', // 商品海报
|
||||
title: activity.value.name, // 商品标题
|
||||
image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
|
||||
price: fen2yuan(state.goodsInfo.price), // 商品价格
|
||||
marketPrice: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const activity = ref();
|
||||
const timeStatusEnum = ref('');
|
||||
|
||||
// 查询活动
|
||||
const getActivity = async (id) => {
|
||||
const { data } = await SeckillApi.getSeckillActivity(id);
|
||||
if (!data) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
activity.value = data;
|
||||
timeStatusEnum.value = getTimeStatusEnum(activity.value.startTime, activity.value.endTime);
|
||||
state.percent = 100 - (data.stock / data.totalStock) * 100;
|
||||
// 查询商品
|
||||
await getSpu(data.spuId);
|
||||
};
|
||||
|
||||
// 查询商品
|
||||
const getSpu = async (id) => {
|
||||
const { data } = await SpuApi.getSpuDetail(id);
|
||||
if (!data) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
return;
|
||||
}
|
||||
data.activity_type = 'seckill';
|
||||
state.goodsInfo = data;
|
||||
// 处理轮播图
|
||||
state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls);
|
||||
|
||||
// 默认显示最低价
|
||||
state.goodsInfo.price = min([
|
||||
state.goodsInfo.price,
|
||||
...activity.value.products.map((spu) => spu.seckillPrice),
|
||||
]);
|
||||
|
||||
// 价格、库存使用活动的
|
||||
data.skus.forEach((sku) => {
|
||||
const product = activity.value.products.find((product) => product.skuId === sku.id);
|
||||
if (product) {
|
||||
sku.price = product.seckillPrice;
|
||||
sku.stock = Math.min(sku.stock, product.stock);
|
||||
} else {
|
||||
// 找不到可能是没配置,则不能发起秒杀
|
||||
sku.stock = 0;
|
||||
}
|
||||
// 设置限购数量
|
||||
if (activity.value.totalLimitCount > 0 && activity.value.singleLimitCount > 0) {
|
||||
sku.limitCount = Math.min(activity.value.totalLimitCount, activity.value.singleLimitCount);
|
||||
} else if (activity.value.totalLimitCount > 0) {
|
||||
sku.limitCount = activity.value.totalLimitCount;
|
||||
} else if (activity.value.singleLimitCount > 0) {
|
||||
sku.limitCount = activity.value.singleLimitCount;
|
||||
}
|
||||
});
|
||||
|
||||
state.skeletonLoading = false;
|
||||
};
|
||||
|
||||
onLoad((options) => {
|
||||
// 非法参数
|
||||
if (!options.id) {
|
||||
state.goodsInfo = null;
|
||||
state.skeletonLoading = false;
|
||||
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;
|
||||
|
||||
&::before {
|
||||
content: '¥';
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
Reference in New Issue
Block a user