提交 201a32b5 作者: ZhangLingKun

功能:商品详情页面

上级 f0bcd73c
...@@ -54,8 +54,11 @@ const MallView: React.FC<{ ...@@ -54,8 +54,11 @@ const MallView: React.FC<{
}; };
// 根据分页数据返回商品列表 // 根据分页数据返回商品列表
const getGoodsInfoListByPage = () => { const getGoodsInfoListByPage = () => {
const { pageNo, pageSize } = pagination; const { pageNo, pageSize, totalCount } = pagination;
return goodsInfoList?.slice((pageNo - 1) * pageSize, pageNo * pageSize); return goodsInfoList?.slice(
(pageNo - 1) * pageSize,
pageNo * (pageSize < totalCount ? pageSize : totalCount),
);
}; };
// 翻页回调 // 翻页回调
const handlePageChange = (pageNo: number, pageSize: number) => { const handlePageChange = (pageNo: number, pageSize: number) => {
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { EnvironmentOutlined } from '@ant-design/icons';
import { InputNumber } from 'antd'; import { InputNumber } from 'antd';
import Big from 'big.js'; import Big from 'big.js';
import dayjs from 'dayjs';
import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { AppMallGoodsDetails } from '@/api/interface/mall'; import { AppMallGoodsDetails } from '@/api/interface/mall';
import ProductSwiperView from '@/components/product-swiper'; import ProductSwiperView from '@/components/product-swiper';
import { RootState } from '@/store';
import { AddressState } from '@/store/module/address';
// 商品详情类型 // 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>; type DetailType = InterDataType<AppMallGoodsDetails>;
const ProductHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => { const ProductHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => {
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// 当前选中的规格
const [selectSpecList, setSelectSpecList] = useState<
{ row: number; index: number }[]
>([]);
// 商品的购买数量 // 商品的购买数量
const [productNum, setProductNum] = useState<number>(1); const [productNum, setProductNum] = useState<number>(1);
// 获取最低价格 // 获取后天的日期
const getLowerPrice = (item: DetailType) => { const getAfterTomorrow = () => {
const price = return dayjs().add(2, 'day').format('MM月DD日');
Math.min(...(item.priceStock?.map((i) => i.salePrice) || [0])) || 0; };
// 判断当前选项是否选中
const isSelect = (row: number, index: number) => {
return !!selectSpecList.find((i) => i.row === row && i.index === index);
};
// 选中项目
const handleSelect = (row: number, index: number) => {
let arr = selectSpecList;
// 选中列表中是否有当前选项
if (arr.find((i) => i.row === row && i.index === index)) {
// 如果有就删除
setSelectSpecList(arr.filter((i) => i.row !== row || i.index !== index));
return;
}
// 如果是单选,就删除当前组别的所有选项
arr = arr.filter((i) => i.row !== row);
// 将当前选项添加到选中列表中
arr = [...arr, { row, index }].sort((a, b) => a.row - b.row);
setSelectSpecList(arr);
};
// 获取当前最便宜的规格,然后选中
const getLowerSpec = () => {
const item = detail?.priceStock
?.sort((a, b) => a.salePrice - b.salePrice)
?.at(0)?.productSpec;
const arr = Object.entries(JSON.parse(item || '{}'))?.map((i) => {
const index = detail?.specAttrList?.findIndex((k) => k.specName === i[0]);
return {
row: index,
index: detail?.specAttrList?.[index]?.specValuesList?.findIndex(
(k) => k.specName === i[1],
),
};
});
setSelectSpecList(arr || []);
};
// 获取规格中最低的价格
const getLowerPrice = () => {
return detail?.priceStock?.sort((a, b) => a.salePrice - b.salePrice)?.at(0)
?.salePrice;
};
// 获取当前规格的价格
const getSpecPrice = () => {
let price = 0;
if (selectSpecList?.length !== detail?.specAttrList?.length) {
price = getLowerPrice() || 0;
} else {
const item = selectSpecList?.map((i) => {
const row = detail?.specAttrList?.[i.row];
const index = row?.specValuesList?.[i.index];
return [row?.specName, index?.specName];
});
price =
detail?.priceStock?.find(
(i) => i.productSpec === JSON.stringify(Object.fromEntries(item)),
)?.salePrice || 0;
}
const money = Big(price).mul(Big(productNum)).toNumber().toLocaleString(); const money = Big(price).mul(Big(productNum)).toNumber().toLocaleString();
return money.endsWith('.00') ? money : `${money}.00`; return money.endsWith('.00') ? money : `${money}.00`;
}; };
useEffect(() => { useEffect(() => {
console.log('detail --->', detail); if (!detail) return;
}, []); getLowerSpec();
// console.log('detail --->', detail);
}, [detail]);
return ( return (
<ProductHeadWrap> <ProductHeadWrap>
<div className="product-swiper"> <div className="product-swiper">
...@@ -36,25 +107,52 @@ const ProductHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => { ...@@ -36,25 +107,52 @@ const ProductHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => {
{detail?.priceShow ? ( {detail?.priceShow ? (
<> <>
<span className="label"></span> <span className="label"></span>
<span className="num">{getLowerPrice(detail)}</span> <span className="num">{getSpecPrice()}</span>
</> </>
) : ( ) : (
<span className="label">咨询报价</span> <span className="label">咨询报价</span>
)} )}
</div> </div>
</div> </div>
<div className="content-item flex-start"> <div className="content-item flex-start align-start">
<div className="item-label">送至</div> <div className="item-label">送至</div>
<div className="item-content"> <div className="item-content">
{detail?.priceStock?.length || 0}种可选 <div className="content-address flex-start">
<EnvironmentOutlined
style={{ color: '#ff6700', fontSize: '12px' }}
/>
<span className="text">{address?.province}</span>
<span className="text">{address?.city}</span>
</div>
<div className="content-address">
现货,今天22:00前下单,最快预计后天({getAfterTomorrow()})送达
</div>
</div> </div>
</div> </div>
<div className="content-item flex-start"> <div className="content-item flex-start">
<div className="item-label">规格</div> <div className="item-label">规格</div>
<div className="item-content"> <div className="item-content">
{detail?.priceStock?.length || 0}种可选 {detail?.priceStock?.length || 0} 种可选
</div> </div>
</div> </div>
{detail?.specAttrList?.map((i, j) => (
<div className="content-item flex-start" key={j}>
<div className="item-label">{i.specName}</div>
<div className="item-content flex-start ">
{i.specValuesList?.map((n, m) => (
<div
className={`content-spec cursor-pointer select-none ${
isSelect(j, m) && 'spec-active'
}`}
key={m}
onClick={() => handleSelect(j, m)}
>
{n.specName}
</div>
))}
</div>
</div>
))}
<div className="content-item flex-start"> <div className="content-item flex-start">
<div className="item-label">数量</div> <div className="item-label">数量</div>
<div className="item-content"> <div className="item-content">
...@@ -83,7 +181,7 @@ export default ProductHeadView; ...@@ -83,7 +181,7 @@ export default ProductHeadView;
const ProductHeadWrap = styled.div` const ProductHeadWrap = styled.div`
position: relative; position: relative;
width: calc((100% - 0.83rem) / 10 * 7.5); width: calc((100% - 0.83rem) / 10 * 7.5);
height: 28rem; min-height: 28rem;
background: #ffffff; background: #ffffff;
border: 0.04rem solid #e3e3e3; border: 0.04rem solid #e3e3e3;
box-sizing: border-box; box-sizing: border-box;
...@@ -91,18 +189,19 @@ const ProductHeadWrap = styled.div` ...@@ -91,18 +189,19 @@ const ProductHeadWrap = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
padding: 1rem; padding: 0 1rem;
.product-swiper { .product-swiper {
position: relative; position: relative;
width: 22rem; width: 22rem;
height: 100%; height: 26rem;
box-sizing: border-box; box-sizing: border-box;
} }
.product-content { .product-content {
position: relative; position: relative;
width: calc(100% - 22rem); width: calc(100% - 22rem);
height: 100%;
box-sizing: border-box; box-sizing: border-box;
padding-left: 1rem; padding: 1rem 0 1rem 1rem;
//background: lightblue; //background: lightblue;
.content-title { .content-title {
width: 100%; width: 100%;
...@@ -128,16 +227,18 @@ const ProductHeadWrap = styled.div` ...@@ -128,16 +227,18 @@ const ProductHeadWrap = styled.div`
box-sizing: border-box; box-sizing: border-box;
padding: 0.8rem; padding: 0.8rem;
margin-bottom: 1rem; margin-bottom: 1rem;
align-items: baseline;
.price-label { .price-label {
width: 2rem; width: 2rem;
color: #999999; color: #999999;
text-align: justify; text-align: justify;
text-align-last: justify; text-align-last: justify;
margin-right: 1rem; margin-right: 1rem;
transform: translateY(-0.1rem);
} }
.price-money { .price-money {
font-size: 24px; font-size: 24px;
line-height: 1; //line-height: 1;
color: #ff6700; color: #ff6700;
.label { .label {
font-size: 18px; font-size: 18px;
...@@ -154,8 +255,46 @@ const ProductHeadWrap = styled.div` ...@@ -154,8 +255,46 @@ const ProductHeadWrap = styled.div`
text-align-last: justify; text-align-last: justify;
margin-right: 1rem; margin-right: 1rem;
} }
.item-content {
.content-address {
font-size: 12px;
&:first-child {
margin-bottom: 0.3rem;
.text {
color: #8e8e8e;
}
}
.text {
margin-left: 0.3rem;
font-weight: 400;
}
}
.content-spec {
//min-width: 4rem;
height: 2rem;
background: #f2f2f2;
border-radius: 0.08rem;
text-align: center;
line-height: 2rem;
font-weight: 400;
box-sizing: border-box;
padding: 0 1rem;
border: 0.04rem solid #f2f2f2;
&:not(:last-child) {
margin-right: 0.5rem;
}
}
.spec-active {
background: #ffede8;
border: 0.04rem solid #ff552d;
color: #ff552d;
}
}
} }
.content-action { .content-action {
//position: absolute;
//left: 1rem;
//bottom: 0;
position: relative; position: relative;
width: 100%; width: 100%;
margin-top: 2rem; margin-top: 2rem;
......
import React from 'react'; import React, { useEffect } from 'react';
import { PhoneOutlined, ShopOutlined } from '@ant-design/icons';
import { Rate } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
import { InterDataType } from '@/api/interface'; import { InterDataType } from '@/api/interface';
import { AppMallGoodsDetails } from '@/api/interface/mall'; import {
AppMallGoodsDetails,
GetCompanyInfoByBUId,
} from '@/api/interface/mall';
// 商品详情类型 // 商品详情类型
type DetailType = InterDataType<AppMallGoodsDetails>; type DetailType = InterDataType<AppMallGoodsDetails>;
// 商城详情类型
type StoreType = InterDataType<GetCompanyInfoByBUId>;
const ProductStoreView: React.FC<{ detail: DetailType; store: StoreType }> = ({
store,
}) => {
useEffect(() => {
console.log('store --->', store);
}, []);
return (
<ProductStoreWrap>
<div className="store-card flex-start">
<img
className="image"
src={store?.brandLogo}
alt={store?.companyName}
/>
<div className="card-content">
<div className="title">{store?.companyName}</div>
<div className="star flex-start">
<div className="tag select-none">店铺星级</div>
<Rate allowHalf defaultValue={5} style={{ fontSize: '10px' }} />
</div>
<div className="text two-line-ellipsis" title={store?.content}>
{store?.content}
</div>
</div>
</div>
<div className="store-item flex-start">
<div className="item-label">地址:</div>
<div className="item-value">{store?.address}</div>
</div>
<div className="store-item flex-start">
<div className="item-label">电话:</div>
<div className="item-value">{store?.phoneNum}</div>
</div>
<div className="store-action flex-start">
<div className="action-item flex-start">
<ShopOutlined style={{ color: '#A8A8A8' }} />
<div className="text">进店逛逛</div>
</div>
<div className="action-item flex-start">
<PhoneOutlined
style={{ color: '#FF552D', transform: 'rotateY(180deg)' }}
/>
<div className="text">进店逛逛</div>
</div>
</div>
</ProductStoreWrap>
);
};
export default ProductStoreView;
// 样式 // 样式
const ProductStoreWrap = styled.div` const ProductStoreWrap = styled.div`
position: relative; position: relative;
width: calc((100% - 0.83rem) / 10 * 2.5); width: calc((100% - 0.83rem) / 10 * 2.5);
height: 28rem; min-height: 28rem;
background: #ffffff; background: #ffffff;
border: 0.04rem solid #e3e3e3; border: 0.04rem solid #e3e3e3;
box-sizing: border-box; box-sizing: border-box;
padding: 1rem;
.store-card {
width: 100%;
min-height: 8em;
background: linear-gradient(270deg, #5f5f5f 0%, #060606 100%);
border-radius: 0.33rem;
box-sizing: border-box;
padding: 0.58rem 1rem;
margin-bottom: 1rem;
align-items: flex-start;
.image {
width: 3.25rem;
height: 3.25rem;
border: 0.02rem solid #e3e3e3;
margin-right: 0.67rem;
}
.card-content {
position: relative;
width: calc(100% - 3.25rem - 0.67rem);
.title {
font-weight: 500;
color: #ffffff;
}
.star {
margin-bottom: 0.5rem;
.tag {
position: relative;
height: 1.2rem;
line-height: 1rem;
background: #fff3ee;
border-radius: 0.13rem;
border: 0.02rem solid #ff552d;
box-sizing: border-box;
padding: 0 0.33rem;
text-align: center;
font-size: 10px;
color: #ff552d;
margin-right: 0.25rem;
transform: scale(0.8);
}
}
.text {
font-size: 12px;
font-weight: 400;
color: rgba(255, 255, 255, 0.8);
}
}
}
.store-item {
align-items: flex-start;
flex-wrap: nowrap;
margin-bottom: 1rem;
.item-label {
width: 3rem;
color: #666666;
}
.item-value {
width: calc(100% - 3rem);
font-size: 12px;
font-weight: 400;
color: #666666;
}
}
.store-action {
position: relative;
width: 100%;
.action-item {
position: relative;
background: #ffffff;
box-sizing: border-box;
padding: 0.33rem 1rem;
border: 0.04rem solid #a8a8a8;
cursor: pointer;
.text {
color: #a8a8a8;
font-weight: 400;
font-size: 12px;
margin-left: 0.33rem;
}
&:not(:last-child) {
margin-right: 0.83rem;
}
&:last-child {
border: 0.04rem solid #ff552d;
.text {
color: #ff552d;
}
}
&:hover {
filter: brightness(0.95);
}
}
}
`; `;
const ProductStoreView: React.FC<{ detail: DetailType }> = ({ detail }) => {
return <ProductStoreWrap>ProductStoreView</ProductStoreWrap>;
};
export default ProductStoreView;
...@@ -30,7 +30,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { ...@@ -30,7 +30,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const res = await MallAPI.appMallGoodsDetails({ id }); const res = await MallAPI.appMallGoodsDetails({ id });
if (res && res.code === '200') { if (res && res.code === '200') {
productDetail = res.result; productDetail = res.result;
console.log('获取商品详情 --->', res); // console.log('获取商品详情 --->', res);
} }
}; };
// 获取店铺详情 // 获取店铺详情
...@@ -40,7 +40,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { ...@@ -40,7 +40,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
}); });
if (res && res.code === '200') { if (res && res.code === '200') {
storeDetail = res.result; storeDetail = res.result;
console.log('获取店铺详情 --->', res); // console.log('获取店铺详情 --->', res);
} }
}; };
// 依次获取接口数据 // 依次获取接口数据
...@@ -63,9 +63,15 @@ const MallProductView: React.FC<{ ...@@ -63,9 +63,15 @@ const MallProductView: React.FC<{
<BreadcrumbView /> <BreadcrumbView />
<div className="flex-start"> <div className="flex-start">
<ProductHeadView detail={productDetail} /> <ProductHeadView detail={productDetail} />
<ProductStoreView detail={productDetail} /> <ProductStoreView detail={productDetail} store={storeDetail} />
</div>
<div className="product-title">商品详情</div>
<div className="product-content">
<div
className="content-html"
dangerouslySetInnerHTML={{ __html: productDetail?.goodsDetails }}
/>
</div> </div>
{productDetail?.tradeName}
</ProductWrap> </ProductWrap>
</LayoutView> </LayoutView>
); );
......
...@@ -11,4 +11,36 @@ export const ProductWrap = styled.div` ...@@ -11,4 +11,36 @@ export const ProductWrap = styled.div`
box-sizing: border-box; box-sizing: border-box;
padding: 2rem 0 0 0; padding: 2rem 0 0 0;
margin: 0 auto; margin: 0 auto;
.product-title {
position: relative;
width: 100%;
text-align: center;
padding: 1.5rem 0;
color: #989898;
font-size: 14px;
&::after,
&::before {
position: absolute;
content: '';
top: 50%;
left: 40%;
width: 4.74rem;
height: 0.02rem;
background: #d4d4d4;
}
&::before {
left: auto;
right: 40%;
}
}
.product-content {
position: relative;
width: 100%;
.content-html {
width: 100%;
img {
width: 100%;
}
}
}
`; `;
...@@ -164,7 +164,19 @@ a { ...@@ -164,7 +164,19 @@ a {
-webkit-user-select: none; -webkit-user-select: none;
-ms-user-select: none; -ms-user-select: none;
} }
.align-start {
align-items: flex-start;
}
.cursor-pointer {
cursor: pointer;
}
.two-line-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.scroll-view::-webkit-scrollbar { .scroll-view::-webkit-scrollbar {
background-color: transparent; background-color: transparent;
-webkit-border-radius: 2em; -webkit-border-radius: 2em;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论