1、mall.vue
<template>
<view class="container">
<!-- 头部背景 -->
<view class="fixed-header">
<NavBar
titleTxt=""
showLeft
bgColor="transparent"
textColor="#FFFFFF"
leftArrowColor="#FFFFFF"
/>
<image
:src="(mallInfo.imgs && mallInfo.imgs[0]) ? mallInfo.imgs[0].imgUrl : `${baseImgHost}/default-img1.png`"
mode="aspectFill"
@tap="toMallImgs"
/>
</view>
<view class="relative-container">
<!-- 商家信息 -->
<view class="mall-info">
<view
class="name-phone flex f-ai-c f-jc-sb"
@tap="makeCall"
>
<view class="left flex f-d-c">
<text class="fz-40">
{{ mallInfo.merchantName }}
</text>
<text class="fz-24">
{{ mallInfo.phone }}
</text>
</view>
<image
:src="`${baseImgHost}/phone3-g.png`"
mode="aspectFill"
/>
</view>
<view
class="address flex f-d-c"
@tap="switchLocation"
>
<text class="fz-24 p-name">
{{ mallInfo.address }}
</text>
<text class="fz-24 p-dis">
距您{{ shortenNumber(mallInfo.meter) }}{{ mallInfo.meter > 1000 ? 'km' : 'm' }}
</text>
<image
class="nav-bg"
:src="`${baseImgHost}/navigation-bg.png`"
mode="aspectFill"
/>
<image
class="nav icon-box"
:src="`${baseImgHost}/navigation.png`"
mode="aspectFill"
/>
</view>
</view>
<!-- 商品分类列表+商品列表 -->
<view class="goods-type-g">
<BgTitle
title="商品列表"
:fontSize="30"
style="margin-left: 20rpx;"
/>
<view class="type-goods-container flex">
<!-- 商品类别滚动区域 -->
<view class="goods-type">
<view
v-for="item in Object.values(goodsTypeList)"
:key="item.id"
class="goods-type-item flex f-jc-c f-ai-c"
:class="{active: item.id === activeTypeId}"
@tap="goodsTypeClickHandle(item)"
>
<view class="fz-24 type-name flex f-jc-c f-ai-c">
{{ item.typeName }}
<view
v-if="item.count"
class="badge fz-20"
:class="[item.count > 9 ? 'rectangle' : 'circle']"
>
{{ item.count }}
</view>
</view>
</view>
</view>
<!-- 商品滚动区域 -->
<view
v-if="goodsList.length"
class="flex flex1 f-d-c"
style="height: 100%; position: relative;"
>
<view
v-for="item in goodsList"
:key="item.id"
class="goods-item flex"
@tap.stop="switchGoods(item.id)"
>
<image
class="goods-img"
:src="item.imgs[0]?.imgUrl || `${baseImgHost}/default-img2.png`"
mode="aspectFill"
/>
<view class="flex f-d-c f-jc-sb goods-info">
<text class="goods-name fz-28">
{{ item.goodsName }}
</text>
<view class="flex f-jc-sb f-ai-fe">
<view class="flex f-d-c">
<text class="goods-sell fz-24">
月销 {{ item.monthSell }} 单
</text>
<text class="goods-price fz-24">
¥<text class="fz-34">
{{ item.specifications[0].price }}
</text>
</text>
</view>
<!-- 选规格 -->
<view
v-if="item.specifications.length > 1"
class=" count-opt specification fz-26"
>
<view
class="gg"
@tap.stop="specificationClick(item)"
>
选规格
<text
v-if="item.count"
class="spec-item-count fz-20"
:class="[item.count > 9 ? 'rectangle' : 'circle']"
>
{{ item.count }}
</text>
</view>
</view>
<!-- 增减数量 -->
<view
v-else
class="flex f-ai-c count-opt"
>
<view
v-if="item.count"
class="opt-b reduce fz-36"
@tap.stop="goodsCountOpt($event, item)"
>
-
</view>
<text
v-if="item.count"
class="fz-28 choosed"
>
{{ item.count }}
</text>
<view
class="opt-b add fz-36"
@tap.stop="goodsCountOpt($event, item, 1)"
>
+
</view>
</view>
</view>
</view>
</view>
</view>
<NoData
v-else
style="margin-top: 20rpx; height: 290rpx;"
/>
</view>
<view class="fixed-bottom" />
</view>
</view>
<!-- 底部下单、购物袋等 -->
<view class="func-bar flex f-ai-c f-jc-sb">
<view class="flex f-ai-c">
<view
class="shopping-bag"
@tap.stop="bagClickHandle($event)"
>
<image
id="bagIcon"
:src="`${baseImgHost}/bag.png`"
mode="aspectFie"
/>
<view
v-if="total"
class="badge fz-20"
:class="[total > 9 ? 'rectangle' : 'circle']"
>
{{ total }}
</view>
<view
v-else
class="badge-hidden"
/>
</view>
<text class="fz-24 total-price">
¥ <text class="fz-34">
{{ totalPrice }}
</text>
</text>
<text class="fz-22 total">
共 {{ total }} 件
</text>
</view>
<view
class="place-order fz-30"
@tap="confirmOrder"
>
去下单
</view>
</view>
<!-- 选规格弹框 -->
<Overlay
:show="showSpecification"
@overlayClick="closeSpecDialog"
/>
<view
class="spec"
:class="showSpecContainer ? 'show' : 'hidden'"
>
<view class="spec-container flex f-d-c">
<view class="goods-name fz-34">
{{ specGoods?.goodsName }}
</view>
<!-- 规格信息 -->
<view class="flex flex1 f-d-c spec-con">
<text class="fz-24">
规格
</text>
<view class="specs flex">
<view
v-for="(item, index) in specGoods?.specifications"
:key="item.id"
class="spec-item fz-24"
:class="{active: index === activeSpecIndex}"
@tap.stop="specClick(item, index)"
>
{{ item.name }}
</view>
</view>
</view>
<text class="spec-choosed fz-24">
已选择规格:{{ specGoods?.specNames?.join(',') }}
</text>
<!-- 购买数量 -->
<view class="flex f-jc-sb f-ai-c spec-count-opt">
<view class="unit-price flex f-ai-c">
<text
class="fz-28"
style="font-weight: 600;"
>
单价
</text>
<view style="color: #FF4200;">
<text class="fz-24">
¥
</text>
<text
class="fz-38"
style="font-weight: 600;"
>
{{ specGoods?.specifications[activeSpecIndex].price }}
</text>
</view>
</view>
<view
v-if="!specGoods?.specifications[activeSpecIndex].count"
class="add-bag flex f-jc-c f-ai-c"
@tap.stop="specCountOptThrottle($event, specGoods?.specifications[activeSpecIndex], specGoods, 1)"
>
<text class="fz-26">
+
</text>
<text class="fz-26">
加入购物袋
</text>
</view>
<view
v-else
class="flex f-ai-c count-opt"
>
<view
class="opt-b reduce fz-28"
@tap.stop="specCountOptThrottle($event, specGoods?.specifications[activeSpecIndex], specGoods)"
>
-
</view>
<text class="fz-28 choosed">
{{ specGoods?.specifications[activeSpecIndex].count || 0 }}
</text>
<view
class="opt-b add fz-28"
@tap.stop="specCountOptThrottle($event, specGoods?.specifications[activeSpecIndex], specGoods, 1)"
>
+
</view>
</view>
</view>
<image
class="close-btn"
:src="`${baseImgHost}/close-2.png`"
mode="aspectFill"
@tap.stop="closeSpecDialog"
/>
</view>
</view>
<!-- 购物袋 -->
<PageContainer
:show="showBag"
height="70vh"
:round="true"
:z-index="9990"
:showShadow="true"
@overlayClick="showBag = false;"
>
<view class="bag-container">
<view class="flex f-ai-c f-jc-sb bag-header">
<view>
<text class="fz-28">
购物袋
</text>
<text class="fz-24 total">
(共{{ total }}件商品)
</text>
</view>
<view class="flex f-ai-c">
<image :src="`${baseImgHost}/del.png`" />
<text
class="fz-24 total"
@tap.stop="clearBagHandle"
>
清空购物袋
</text>
</view>
</view>
<view class="bg-list">
<view
v-for="item in Object.values(shoppingBag)"
:key="item.id"
class="bg-item flex f-jc-sb"
>
<view class="flex">
<image :src="(item.goodsImgs ?? [])[0]?.imgUrl || `${baseImgHost}/default-img2.png`" />
<view class="flex f-d-c">
<text class="fz-28">
{{ item.goodsName }}
</text>
<text
class="fz-24"
style="color: #828180;"
>
{{ item.specificationName }}
</text>
<view
class="fz-20"
style="color: #FF4200"
>
¥<text class="fz-28">
{{ item.price }}
</text>
</view>
</view>
</view>
<view
class="flex f-ai-c count-opt"
>
<view
class="opt-b reduce fz-36"
@tap.stop="bagCoutOpt(item)"
>
-
</view>
<text
class="fz-28 choosed"
>
{{ item.count }}
</text>
<view
class="opt-b add fz-36"
@tap.stop="bagCoutOpt(item, 1)"
>
+
</view>
</view>
</view>
</view>
</view>
</PageContainer>
<view
class="dot"
:style="{ opacity: dotOpacity, left: dotLeft, top: dotTop }"
/>
</view>
</template>
<script>
import './mall.less';
import { NavBar, BgTitle, Overlay, PageContainer, NoData } from '@/components';
import Taro from '@tarojs/taro';
import { throttle, twoBezier } from '@/utils/util';
import { shortenNumber } from '@/utils/number';
import { getGoodsTypeList, getGoodsList, merchantDetail, bagAdd, bagList, bagDelete, bagClear } from '@/apis/goods';
export default {
name: 'MallDetail',
components: { NavBar, BgTitle, Overlay, PageContainer, NoData },
provide () {
return {
activeBackground: 'linear-gradient(90deg, #FFA00C, rgba(255,159,10,0)) !important'
};
},
data () {
return {
isInit: true, // 是否是页面数据初始化
showBag: false,
merchantId: null, // 商家ID
keyword: '',
mallInfo: {}, // 商家信息
goodsList: [], // 商品列表
goodsTypeList: {}, // 商品类型列表
shortenNumber,
activeTypeId: 0, // 当前选中的商品类型id
activeType: null, // 当前选中的商品类型
showSpecification: false, // 是否展示选规格弹框
showSpecContainer: false, // 是否展示选规格容器
activeSpecIndex: 0, // 任何一个规格弹框中,选中的规格的索引
specGoods: null, // 当前查看规格的商品
/**
* 购物袋, key: 某个规格的某个商品的记录id;
*/
shoppingBag: {},
totalPrice: 0, // 总价
total: 0, // 总数量
pager: {
currentPage: 1,
pageSize: 10,
pageCount: 0
},
specCountOptThrottle: null,
dotTop: 0,
dotLeft: 0,
endLeft: 0,
endTop: 0,
dotOpacity: 0
};
},
onLoad (options) {
this.merchantId = options.id;
},
async onShow () {
this.pager = {
currentPage: 1,
pageSize: 10,
pageCount: 0
};
this.goodsList = [];
this.wxToken = Taro.getStorageSync('token');
this.initLngLat(this.getMallInfo);
const storageActiveTypeId = await Taro.getStorageSync('activeGoodsType');
if (storageActiveTypeId) Taro.removeStorageSync('activeGoodsType');
await this.getGoodsTypeList(storageActiveTypeId);
await this.getGoodsList(this.activeTypeId);
this.specCountOptThrottle = throttle(this.specCountOpt, 500);
// 初始化购物袋的位置坐标
const query = Taro.createSelectorQuery();
query.select('#bagIcon').boundingClientRect((res) => {
this.endLeft = res.left + 12.5;
this.endTop = res.top;
}).exec();
},
onHide () {
this.showBag = false;
},
methods: {
makeCall () {
if (this.mallInfo.phone) {
Taro.makePhoneCall({
phoneNumber: this.mallInfo.phone
}).catch(() => {});
}
},
switchLocation () {
const that = this;
Taro.openLocation({
latitude: Number(that.mallInfo.lat),
longitude: Number(that.mallInfo.lng),
scale: 18,
name: that.mallInfo.merchantName,
address: that.mallInfo.address
});
},
clearBagHandle () {
bagClear({ merchantId: this.merchantId }, this.wxToken, false).then(async res => {
if (res) {
Taro.showToast({
title: '操作成功',
icon: 'none',
duration: 1000
});
await this.getGoodsTypeList();
await this.getGoodsList(this.activeTypeId);
}
});
},
toMallImgs () {
Taro.navigateTo({ url: `/pages/mallImgs/mallImgs?type=mall&merchantId=${this.merchantId}` });
},
confirmOrder () {
if (this.total) {
Taro.navigateTo({ url: `/pages/orderConfirm/orderConfirm?merchantId=${this.merchantId}` });
}
},
// 购物袋被点击
bagClickHandle () {
if (!this.total) return;
if (this.showBag) {
this.showBag = false;
return;
}
this.showBag = true;
this.getBagList();
},
// 获取商家信息
getMallInfo () {
merchantDetail(this.merchantId, { lat: this.lat, lng: this.lng }, this.wxToken).then(res => {
this.mallInfo = res;
});
},
// 商品类型列表
async getGoodsTypeList (storageActiveTypeId) {
const res = await getGoodsTypeList({ merchantId: this.merchantId, page: 1, limit: 100 }, this.wxToken);
if (res && res.list.length) {
if (storageActiveTypeId) {
this.activeTypeId = storageActiveTypeId;
this.activeType = res.list.find(l => l.id === storageActiveTypeId);
} else {
this.activeTypeId = res.list[0].id;
this.activeType = res.list[0];
}
res.list.map(l => {
this.goodsTypeList[l.id] = { ...l, count: 0 };
});
}
},
// 商品列表
async getGoodsList (id, showLoading) {
const res = await getGoodsList({
merchantId: this.merchantId,
typeId: id,
page: this.pager.currentPage,
limit: this.pager.pageSize
}, this.wxToken, showLoading);
if (this.triggered) {
this.triggered = false;
Taro.stopPullDownRefresh();
}
if (res && res.list) {
if (this.pager.currentPage === 1) {
this.goodsList = res.list;
} else {
this.goodsList = this.goodsList.concat(res.list);
}
this.pager = res.page;
}
this.getBagList();
},
// 获取购物袋中物品列表
getBagList () {
this.goodsList = this.goodsList.map(gl => {
gl.count = 0;
gl.specifications = gl.specifications.map(sp => {
sp.count = 0;
return sp;
});
return gl;
});
bagList({ merchantId: this.merchantId }, this.wxToken).then(res => {
this.shoppingBag = {};
// 购物袋每条记录中带有该商品的类别id,字段名为: typeId
if (res && res.length) {
const _goodsTypeList = {};
const { count, price } = res.reduce((pre, curr, index, arr) => {
// 填充购物袋物品
this.shoppingBag[curr.id] = curr;
// 各商品类型回显已选中的商品总数
if (_goodsTypeList[curr.goodsTypeId]) {
_goodsTypeList[curr.goodsTypeId].count += curr.count;
} else {
_goodsTypeList[curr.goodsTypeId] = { count: curr.count };
}
const goods = this.goodsList.find(g => g.id === curr.goodsId);
if (goods) {
// 各商品回显已选中的商品总数
goods.count = (goods.count ?? 0) + curr.count;
// 某商品中的各个规格回显总数
const spec = goods.specifications.find(s => s.id === curr.specificationId);
if (spec) {
spec.count = curr.count;
}
}
return {
count: pre.count + curr.count,
price: +(pre.price + (curr.price || 0)).toFixed(2)
};
}, { count: 0, price: 0 });
// 各商品类型回显已选中的商品总数
for (const gt in _goodsTypeList) {
this.goodsTypeList[gt].count = _goodsTypeList[gt].count;
}
this.total = count;
this.totalPrice = price;
} else {
this.showBag = false; // 如果购物袋中物品数为0时关闭购物袋
this.total = 0;
this.totalPrice = 0;
for (const key in this.goodsTypeList) {
this.goodsTypeList[key].count = 0;
}
}
});
},
tabClickHandle (id) {
Taro.navigateTo({ url: `/pages/mallDetail/mallDetail?id=${id}` });
},
// 某个商品类别被点击
goodsTypeClickHandle (item) {
this.activeTypeId = item.id;
this.activeType = item;
this.pager.currentPage = 1;
this.getGoodsList(item.id, true);
},
dotAnimate (event) {
// 设置小红点初始位置
const { clientX, clientY } = event.changedTouches[0];
this.dotLeft = clientX + 'px';
this.dotTop = clientY + 'px';
const num = 30;
const track = [];
for (let i = 0; i < num + 1; i++) {
const [x, y] = twoBezier(i / num, [clientX, clientY], [clientX - 60, clientY - 120], [this.endLeft, this.endTop]);
track.push({ opacity: 1, left: `${x}px`, top: `${y}px` });
}
track[num].opacity = 0;
let i = 0;
const inter = setInterval(() => {
if (i === num + 1) {
clearInterval(inter);
return;
}
const { opacity, left, top } = track[i];
this.dotOpacity = opacity;
this.dotLeft = left;
this.dotTop = top;
i++;
}, 10);
},
// 商品列表中的加减号
goodsCountOpt (event, item, type) {
if (!type && !item.count) return;
(type ? bagAdd : bagDelete)({
merchantId: this.merchantId, // 商户id
goodsId: item.id, // 商品id
specificationId: item.specifications[0].id // 规格id
}, this.wxToken).then(res => {
if (res) {
if (type) {
this.dotAnimate(event);
item.count = (item.count ?? 0) + 1;
} else {
item.count--;
}
this.updateOther(type, item.specifications[0].price);
this.updateShoppingBagDatas(item.id, item.specifications[0].id, type, res);
}
});
},
// 更新总数、总价、各商品类型已选购总数
updateOther (type, price) {
if (type) {
this.total++;
this.goodsTypeList[this.activeTypeId].count = (this.goodsTypeList[this.activeTypeId].count ?? 0) + 1;
this.totalPrice = +(this.totalPrice + price).toFixed(2);
} else {
if (this.total) {
this.goodsTypeList[this.activeTypeId].count--;
this.total--;
this.totalPrice = +(this.totalPrice - price).toFixed(2);
}
}
},
// 更新购物袋中的物品
updateShoppingBagDatas (goodsId, specificationId, type, res) {
for (const i in this.shoppingBag) {
if (this.shoppingBag[i].goodsId === goodsId && this.shoppingBag[i].specificationId === specificationId) {
// 如果是添加物品
if (type) {
this.shoppingBag[i].count++;
} else {
this.shoppingBag[i].count--;
if (!this.shoppingBag[i].count) {
delete this.shoppingBag[i];
}
}
}
}
// 将购物袋中没有的物品同步进去
if (type && !this.shoppingBag[res.id]) {
this.shoppingBag[res.id] = res;
}
},
// 点击某个货物
switchGoods (id) {
Taro.setStorageSync('activeGoodsType', this.activeTypeId);
Taro.navigateTo({
url: `/pages/mallGoodsDetail/mallGoodsDetail?id=${id}&merchantId=${this.merchantId}`
});
},
// 商品列表中的规格按钮点击
specificationClick (item) {
item.specNames = [];
this.showSpecification = true;
this.showSpecContainer = true;
item.count = 0;
Object.values(this.shoppingBag).forEach(gr => {
const spec = item.specifications.find(s => {
if (item.id === gr.goodsId && s.id === gr.specificationId) return s;
});
if (spec) {
spec.count = gr.count; // 将购物袋中该商品的某个规格的购买数量赋值给该商品的该规格
item.count = (item.count ?? 0) + gr.count;
item.specNames.push(spec.name);
}
});
this.specGoods = item;
},
// 关闭规格弹框
closeSpecDialog () {
this.activeSpecIndex = 0;
this.showSpecContainer = false;
this.showSpecification = false;
},
// 规格弹框中的某个规格点击
specClick (item, index) {
this.activeSpecIndex = index;
},
// 商品规格弹框中的加减号
specCountOpt (event, item, specGoods, type) {
(type ? bagAdd : bagDelete)({
merchantId: this.merchantId, // 商户id
goodsId: specGoods.id, // 商品id
specificationId: item.id // 规格id
}, this.wxToken).then(res => {
if (res) {
if (type) {
this.dotAnimate(event);
if (!item.count) {
specGoods.specNames = Array.from(new Set([...specGoods.specNames, item.name]));
}
item.count = (item.count ?? 0) + 1; // 该商品选中的某规格的总数
specGoods.count = (specGoods.count ?? 0) + 1; // 该商品选中的各规格的总数
} else {
item.count--;
if (!item.count) {
specGoods.specNames.splice(specGoods.specNames.indexOf(item.name), 1);
}
specGoods.count--;
}
this.updateOther(type, item.price);
this.updateShoppingBagDatas(specGoods.id, item.id, type, res);
}
});
},
// 购物袋弹框中的加减号
bagCoutOpt (item, type) {
(type ? bagAdd : bagDelete)({
merchantId: this.merchantId, // 商户id
goodsId: item.goodsId, // 商品id
specificationId: item.specificationId // 规格id
}, this.wxToken).then(res => {
if (res) {
// 如果是减号,且该商品选购数量==1,则将其从购物袋中清除
if (!type && item.count === 1) {
delete this.shoppingBag[item.id];
}
this.getBagList();
}
});
}
},
async onPullDownRefresh () {
if (this.triggered) return;
this.triggered = true;
await this.getGoodsTypeList();
this.pager = {
currentPage: 1,
pageSize: 10,
pageCount: 0
};
await this.getGoodsList(this.activeTypeId);
},
async onReachBottom () {
this.onTolowerMixin(() => this.getGoodsList(this.activeTypeId));
}
};
</script>
2、mall.less
page {
height: 100vh;
overflow-y: scroll;
.container {
padding-top: 400rpx;
.fixed-header {
position: fixed;
z-index: 998;
top: 0;
width: 100vw;
height: 434rpx;
image {
width: 100vw;
}
}
.relative-container {
width: 100vw;
z-index: 998;
background-color: #FFFFFF;
border-radius: 35rpx 35rpx 0 0;
.mall-info {
margin-top: 38rpx;
padding: 0 20rpx;
box-sizing: border-box;
.name-phone {
.left {
text:nth-child(1) {
font-family: PingFang-SC-Bold;
font-weight: bold;
color: #333333;
}
text:nth-child(2n) {
display: inline-block;
margin-top: 25rpx;
font-family: PingFangSC-Light;
font-weight: 300;
color: #666666;
}
}
image {
width: 50rpx;
height: 50rpx;
}
}
.address {
position: relative;
margin-top: 30rpx;
height: 130rpx;
padding-top: 14rpx;
box-sizing: border-box;
.p-name {
width: 500rpx;
color: #333333;
font-weight: bold;
font-family: PingFang-SC-Bold;
}
.p-dis {
margin-top: 21rpx;
color: #666666;
font-family: PingFangSC-Regular;
font-weight: 400;
}
image {
position: absolute;
}
.nav-bg {
width: 290rpx;
height: 129rpx;
right: 0;
top: 0;
z-index: -1;
}
.nav {
right: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
.goods-type-g {
margin-top: 45rpx;
.type-goods-container {
padding-bottom: 200rpx;
box-sizing: border-box;
.goods-type {
width: 152rpx;
background-color: #F8F8F8;
&-item {
.type-name {
position: relative;
width: 152rpx;
height: 61rpx;
color: #9B9B9B;
background-color: #F8F8F8;
text-align: center;
margin: 18rpx 0;
.badge {
position: absolute;
top: 0;
right: 0rpx;
min-width: 30rpx;
height: 30rpx;
line-height: 30rpx;
text-align: center;
color: #FFFFFF;
background: #FE3A46;
&.rectangle {
padding: 0 8rpx;
border-radius: 16rpx;
}
&.circle {
border-radius: 50%;
}
}
.badge-hidden {
opacity: 0;
position: absolute;
top: 0;
right: 0rpx;
width: 30rpx;
height: 30rpx;
}
}
&.active {
.type-name {
background-color: #FFFFFF !important;
color: #010101 !important;
&::after {
content: '';
position: absolute;
width: 6rpx;
height: 45rpx;
left: 0;
top: 50%;
transform: translateY(-50%);
background: #FFA00C;
border-radius: 0 7rpx 7rpx 0;
}
}
}
}
}
.goods-item {
position: relative;
height: 232rpx;
padding: 25rpx 21rpx;
box-sizing: border-box;
background-color: #FFFFFF;
.goods-img {
width: 183rpx;
height: 183rpx;
border-radius: 30rpx;
margin-right: 21rpx;
}
.goods-info {
// width: 353rpx;
.goods-name {
width: 335rpx;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-weight: 800;
color: #3E3A39;
font-family: PingFang-SC-Heavy;
}
.goods-sell {
display: inline-block;
margin: 10rpx 0 22rpx 0;
font-family: PingFang-SC-Medium;
font-weight: 500;
color: #828180;
}
.goods-price {
width: 115rpx;
color: #FF4200;
font-family: PingFang-SC-Bold;
font-weight: bold;
}
}
.specification {
.gg {
position: relative;
width: 93rpx;
height: 41rpx;
line-height: 41rpx;
font-family: PingFang-SC-Medium;
text-align: center;
background: #FFA00C;
border-radius: 10rpx;
color: #FFFFFF;
.spec-item-count {
display: inline-block;
position: absolute;
top: -16rpx;
right: -13rpx;
min-width: 30rpx;
height: 30rpx;
line-height: 30rpx;
text-align: center;
color: #FFFFFF;
background: #FE3A46;
&.rectangle {
padding: 0 8rpx;
border-radius: 16rpx;
}
&.circle {
border-radius: 50%;
}
}
}
}
.count-opt {
.opt-b {
width: 43rpx;
height: 43rpx;
border-radius: 50%;
line-height: 43rpx;
text-align: center;
}
.reduce {
border: 1rpx solid #FFA00C;
}
.choosed {
display: inline-block;
margin: 0 16rpx;
}
.add {
border: 1rpx solid #FFEBCB;
background: #FFEBCB;
color: #FFA00C;
}
}
}
}
.fixed-bottom {
position: fixed;
width: 100vw;
height: 74rpx;
bottom: 0;
background-color: #FFFFFF;
}
}
}
.func-bar {
position: fixed;
z-index: 9999;
width: 708rpx;
height: 98rpx;
left: 50%;
transform: translateX(-50%);
top: 90vh;
// bottom: 50rpx;
overflow: hidden;
background: #FFFFFF;
box-shadow: 0 2rpx 10rpx 0 rgba(255, 160, 12, 0.2);
border-radius: 49rpx;
.shopping-bag {
position: relative;
width: 52rpx;
height: 60rpx;
margin-left: 42rpx;
margin-right: 38rpx;
image {
width: 52rpx;
height: 60rpx;
}
.badge {
position: absolute;
top: 0;
right: -13rpx;
min-width: 30rpx;
height: 30rpx;
line-height: 30rpx;
text-align: center;
color: #FFFFFF;
background: #FE3A46;
&.rectangle {
padding: 0 8rpx;
border-radius: 16rpx;
}
&.circle {
border-radius: 50%;
}
}
}
.total-price {
display: inline-block;
margin-right: 15rpx;
color: #FF4200;
font-weight: bold;
}
.total {
font-weight: 400;
color: #828180;
}
.place-order {
width: 207rpx;
height: 98rpx;
line-height: 98rpx;
text-align: center;
background-color: #FE3A46;
color: #FFFFFF;
}
}
.spec {
position: fixed;
z-index: 9999;
top: 16vh;
opacity: 0;
width: 710rpx;
height: 58vh;
left: 50%;
transition: all 0.2s ease-in;
transform: translateX(-50%) scale(0);
&.show {
opacity: 1;
transform: translateX(-50%) scale(1) !important;
}
&.hidden {
opacity: 0;
transform: translateX(-50%) scale(0) !important;
}
.spec-container {
position: relative;
width: 100%;
// height: 644rpx;
height: 50vh;
border-radius: 10rpx;
padding-top: 30rpx;
box-sizing: border-box;
background-color: #FFFFFF;
.goods-name {
padding: 0 23rpx;
font-family: PingFangSC-Semibold;
font-weight: 600;
color: #000000;
}
.spec-con {
margin-top: 30rpx;
text {
padding: 0 23rpx;
font-family: PingFangSC-Regular;
font-weight: 400;
color: #8A8A8A;
}
.specs {
margin-top: 20rpx;
padding: 0 23rpx;
height: 100%;
flex-wrap: wrap;
overflow-y: scroll;
.spec-item {
height: 58rpx;
padding: 0 25rpx;
margin-right: 30rpx;
margin-bottom: 20rpx;
line-height: 58rpx;
text-align: center;
background: #F2F2F4;
border: 1rpx solid #F2F2F4;
border-radius: 10rpx;
&.active {
border-color: #FFA00C;
background-color: #FFF6E9;
color: #FFA00C;
}
}
}
}
.spec-choosed {
display: inline-block;
width: 100%;
padding: 22rpx 30rpx;
box-sizing: border-box;
background: #FAFAFA;
color: #656565;
}
.spec-count-opt {
// position: relative;
height: 110rpx;
margin-top: 20rpx;
padding: 5rpx 23rpx;
.add-bag {
position: relative;
width: 185rpx;
height: 61rpx;
line-height: 61rpx;
text-align: center;
background: #FFA00C;
border-radius: 10rpx;
font-family: PingFang SC;
font-weight: 400;
color: #FFFFFF;
text:nth-child(1) {
display: inline-block;
margin-right: 10rpx;
transform: scale(1.5);
}
}
.count-opt {
// position: absolute;
// top: 50%;
// transform: translateY(-50%);
right: 22rpx;
.opt-b {
width: 43rpx;
height: 43rpx;
border-radius: 50%;
line-height: 43rpx;
text-align: center;
}
.reduce {
border: 1rpx solid #FFA00C;
}
.choosed {
display: inline-block;
margin: 0 16rpx;
}
.add {
border: 1rpx solid #FFEBCB;
background: #FFEBCB;
color: #FFA00C;
}
}
}
.close-btn {
position: absolute;
width: 77rpx;
height: 77rpx;
bottom: -130rpx;
left: 50%;
border-radius: 50%;
transform: translateX(-50%);
}
}
}
.bag-container {
position: relative;
height: 100%;
padding: 90rpx 20rpx 170rpx 29rpx;
box-sizing: border-box;
background-color: #FFFFFF;
.bag-header {
position: absolute;
padding: 0 20rpx 0 29rpx;
top: 43rpx;
left: 0;
right: 0;
.total {
color: #A09F9E;
}
image {
width: 24rpx;
height: 27rpx;
margin-right: 13rpx;
}
}
.bg-list {
height: 100%;
overflow-y: scroll;
.bg-item {
position: relative;
padding: 20rpx 0;
image {
width: 113rpx;
height: 113rpx;
border-radius: 20rpx;
margin-right: 20rpx;
}
.count-opt {
position: absolute;
bottom: 28rpx;
right: 22rpx;
.opt-b {
width: 43rpx;
height: 43rpx;
border-radius: 50%;
line-height: 43rpx;
text-align: center;
}
.reduce {
border: 1rpx solid #FFA00C;
}
.choosed {
display: inline-block;
margin: 0 16rpx;
}
.add {
border: 1rpx solid #FFEBCB;
background: #FFEBCB;
color: #FFA00C;
}
}
}
}
}
.dot {
position: fixed;
opacity: 0;
width: 20rpx;
height: 20rpx;
z-index: 9999;
border-radius: 50%;
background-color: #FF4200;
}
}
}
3、mall.config.js
export default definePageConfig({
'navigationStyle': 'custom',
'navigationBarTextStyle': 'white',
'enablePullDownRefresh': true, // 当前页
'backgroundTextStyle': 'dark', // 顶部显示颜色为深色的三个点
onReachBottomDistance: 10
});
4、 twoBezier
/**
* @desc 二阶贝塞尔
* @param {number} t 当前百分比
* @param {Array} p1 起点坐标
* @param {Array} cp 控制点
* @param {Array} p2 终点坐标
*/
const twoBezier = (t, p1, cp, p2) => {
const [x1, y1] = p1;
const [cx, cy] = cp;
const [x2, y2] = p2;
const x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2;
const y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2;
return [x, y];
};