automated_uniapp/components/goods/spec.vue
2025-01-09 16:16:11 +08:00

394 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<cl-popup
v-model="spec.visible"
direction="bottom"
:border-radius="[32, 32, 0, 0]"
:padding="0"
show-close-btn
@open="onOpen"
>
<view class="goods-spec">
<view class="goods">
<goods-cover :item="spec.goods" :spec="spec.info" :link="false" :size="160" />
<view class="det">
<cl-row :margin="[0, 0, 16, 0]">
<cl-text
:value="spec.info?.price || spec.goods?.price"
type="price"
color="error"
:size="40"
bold
/>
<cl-text
color="info"
:size="24"
:margin="[0, 0, 0, 30]"
:value="`库存:${stock}`"
v-if="spec.info"
/>
</cl-row>
<cl-text
:ellipsis="2"
:line-height="1.2"
:color="spec.info ? 'primary' : 'info'"
:value="spec.text"
/>
</view>
</view>
<cl-row type="flex" justify="space-between" :margin="[0, 24, 16, 24]">
<cl-text bold>规格分类({{ list.length + 1 }}</cl-text>
<cl-icon
:size="40"
:class-name="`${isGrid ? 'shop-icon-list' : 'shop-icon-list2'}`"
@tap="isGrid = !isGrid"
/>
</cl-row>
<!-- 规格列表 -->
<cl-loading-mask :loading="loading">
<scroll-view scroll-y class="scroller">
<view
class="list"
:class="{
'is-grid': isGrid,
}"
>
<cl-row :gutter="20">
<cl-col
v-for="(item, label) in list"
:key="label"
:span="isGrid ? 8 : 24"
>
<view
class="item"
:class="{
'is-active': spec.info?.id == item.id,
'is-disabled': !item.stock,
}"
@tap="select(item)"
>
<view class="cover">
<goods-cover
:item="spec.goods"
:spec="item"
:link="false"
:radius="isGrid ? 24 : 12"
:zoom="isGrid"
/>
</view>
<view class="name">
<text>{{ item.name }}</text>
</view>
</view>
</cl-col>
</cl-row>
<cl-list-item
label="数量"
:arrow-icon="false"
:margin="[0, -10, 0, -10]"
v-if="spec.info"
>
<cl-input-number v-model="spec.num" :min="1" :max="stock" />
</cl-list-item>
</view>
</scroll-view>
</cl-loading-mask>
<!-- 底部按钮 -->
<cl-footer :fixed="false" :vt="[spec.action]">
<!-- 加入购物车 -->
<template v-if="spec.action == 'spCart'">
<cl-button custom type="primary" :disabled="disabled" @tap="toAdd"
>确定</cl-button
>
</template>
<!-- 选规格的时候 -->
<template v-else-if="spec.action == 'select'">
<cl-button custom :disabled="disabled" @tap="toAdd">加入购物车</cl-button>
<cl-button custom type="primary" :disabled="disabled" @tap="toBuy"
>立即购买</cl-button
>
</template>
<!-- 立即购买 -->
<template v-else-if="spec.action == 'buy'">
<cl-button custom type="primary" :disabled="disabled" @tap="toBuy"
>立即购买</cl-button
>
</template>
<!-- 编辑 -->
<template v-else-if="spec.action == 'edit'">
<cl-button custom type="primary" :disabled="disabled" @tap="toEdit"
>确定</cl-button
>
</template>
</cl-footer>
</view>
</cl-popup>
</template>
<script lang="ts" setup>
import { isEmpty } from "lodash-es";
import { computed, nextTick, onUnmounted, ref } from "vue";
import { useCool } from "/@/cool";
import { useSpec } from "/@/hooks";
import { useShoppingCart } from "/@/hooks";
import { useUi } from "/$/cool-ui";
import GoodsCover from "/@/components/goods/cover.vue";
const { service, router } = useCool();
const spec = useSpec();
const spCart = useShoppingCart();
const ui = useUi();
// 列表是否格子布局
const isGrid = ref(true);
// 加载规格中
const loading = ref(false);
// 规格列表
const list = ref<Eps.GoodsSpecEntity[]>([]);
// 库存数,减去购物车数量
const stock = computed(() => {
const a = spec.info?.stock || 0;
const b = spCart.list.find((e) => e.spec?.id == spec.info?.id)?.count || 0;
if (spec.action == "edit") {
return a;
}
let d = a - b;
if (d < 0) {
d = 0;
}
return d;
});
// 是否能下单
const disabled = computed(() => {
return !stock.value;
});
// 获取规格列表
async function refresh(params?: any) {
loading.value = true;
await service.goods.spec
.list({
goodsId: spec.goods?.id,
order: "sortNum",
sort: "desc",
...params,
})
.then((res) => {
list.value = res.map((e) => {
let arr = e.images || [];
if (isEmpty(arr)) {
arr = [spec.goods?.mainPic];
}
return {
...e,
cover: arr[0],
};
});
// 设置规格
spec.set(list.value.find((e) => e.id == spec.specId));
});
loading.value = false;
}
// 监听打开
function onOpen() {
refresh();
}
// 选择规格
function select(item: Eps.GoodsSpecEntity) {
if (spec.info?.id == item.id) {
spec.set();
} else {
spec.set(item);
}
nextTick(() => {
spec.setNum(1);
});
}
// 购买
function toBuy() {
spec.close();
router.push({
path: "/pages/order/submit",
params: {
buyList: [
{
spec: spec.info!,
goodsInfo: spec.goods!,
count: spec.num,
},
],
},
});
}
// 编辑
function toEdit() {
spec.emit("edit");
spec.close();
}
// 加入购物车
function toAdd() {
spCart.add({
count: spec.num,
spec: spec.info!,
goodsInfo: spec.goods!,
});
ui.showToast("添加购物车成功");
spec.close();
}
onUnmounted(() => {
spec.clear();
spec.close();
});
defineExpose({
refresh,
});
</script>
<style lang="scss" scoped>
.goods-spec {
.goods {
display: flex;
padding: 24rpx;
.det {
flex: 1;
padding: 24rpx 24rpx 0 24rpx;
}
}
.scroller {
height: 50vh;
.list {
padding: 0 24rpx;
.item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
border-radius: 12rpx;
border: 2rpx solid #eeeeee;
padding: 12rpx;
.cover {
height: 80rpx;
width: 80rpx;
flex-shrink: 0;
position: relative;
}
.name {
display: flex;
align-items: center;
padding: 0 16rpx;
font-size: 24rpx;
line-height: 1.4;
flex: 1;
text {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
&.is-active {
border-color: $cl-color-primary;
}
&.is-disabled {
.cover {
&::after {
display: flex;
align-items: center;
justify-content: center;
height: 100rpx;
width: 100rpx;
content: "无货";
color: #fff;
background-color: rgba(50, 50, 50, 0.6);
border-radius: 100%;
position: absolute;
left: calc(50% - 50rpx);
top: calc(50% - 50rpx);
font-size: 26rpx;
border: 1rpx solid currentColor;
box-sizing: border-box;
transform: scale(0.6);
}
}
.name {
color: $cl-color-disabled;
}
}
}
&.is-grid {
.item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
border-radius: 28rpx;
padding: 0;
margin-bottom: 24rpx;
.cover {
height: 216rpx;
width: 100%;
&::after {
transform: scale(1);
}
}
.name {
flex: none;
height: 70rpx;
line-height: 1.2;
}
}
}
}
}
}
</style>