Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
web
概览
概览
详情
活动
周期分析
版本库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
iuav
web
Commits
201a32b5
提交
201a32b5
authored
12月 08, 2023
作者:
ZhangLingKun
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
功能:商品详情页面
上级
f0bcd73c
隐藏空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
368 行增加
和
30 行删除
+368
-30
index.tsx
src/components/mall-comp/index.tsx
+5
-2
index.tsx
src/components/product-comp/product-head/index.tsx
+154
-15
index.tsx
src/components/product-comp/product-store/index.tsx
+154
-8
[id].tsx
src/pages/mall/product/[id].tsx
+10
-4
styled.tsx
src/pages/mall/product/styled.tsx
+32
-0
globals.css
src/styles/globals.css
+13
-1
没有找到文件。
src/components/mall-comp/index.tsx
浏览文件 @
201a32b5
...
...
@@ -54,8 +54,11 @@ const MallView: React.FC<{
};
// 根据分页数据返回商品列表
const
getGoodsInfoListByPage
=
()
=>
{
const
{
pageNo
,
pageSize
}
=
pagination
;
return
goodsInfoList
?.
slice
((
pageNo
-
1
)
*
pageSize
,
pageNo
*
pageSize
);
const
{
pageNo
,
pageSize
,
totalCount
}
=
pagination
;
return
goodsInfoList
?.
slice
(
(
pageNo
-
1
)
*
pageSize
,
pageNo
*
(
pageSize
<
totalCount
?
pageSize
:
totalCount
),
);
};
// 翻页回调
const
handlePageChange
=
(
pageNo
:
number
,
pageSize
:
number
)
=>
{
...
...
src/components/product-comp/product-head/index.tsx
浏览文件 @
201a32b5
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
EnvironmentOutlined
}
from
'@ant-design/icons'
;
import
{
InputNumber
}
from
'antd'
;
import
Big
from
'big.js'
;
import
dayjs
from
'dayjs'
;
import
{
useSelector
}
from
'react-redux'
;
import
styled
from
'styled-components'
;
import
{
InterDataType
}
from
'@/api/interface'
;
import
{
AppMallGoodsDetails
}
from
'@/api/interface/mall'
;
import
ProductSwiperView
from
'@/components/product-swiper'
;
import
{
RootState
}
from
'@/store'
;
import
{
AddressState
}
from
'@/store/module/address'
;
// 商品详情类型
type
DetailType
=
InterDataType
<
AppMallGoodsDetails
>
;
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
getLowerPrice
=
(
item
:
DetailType
)
=>
{
const
price
=
Math
.
min
(...(
item
.
priceStock
?.
map
((
i
)
=>
i
.
salePrice
)
||
[
0
]))
||
0
;
// 获取后天的日期
const
getAfterTomorrow
=
()
=>
{
return
dayjs
().
add
(
2
,
'day'
).
format
(
'MM月DD日'
);
};
// 判断当前选项是否选中
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
();
return
money
.
endsWith
(
'.00'
)
?
money
:
`
${
money
}
.00`
;
};
useEffect
(()
=>
{
console
.
log
(
'detail --->'
,
detail
);
},
[]);
if
(
!
detail
)
return
;
getLowerSpec
();
// console.log('detail --->', detail);
},
[
detail
]);
return
(
<
ProductHeadWrap
>
<
div
className=
"product-swiper"
>
...
...
@@ -36,25 +107,52 @@ const ProductHeadView: React.FC<{ detail: DetailType }> = ({ detail }) => {
{
detail
?.
priceShow
?
(
<>
<
span
className=
"label"
>
¥
</
span
>
<
span
className=
"num"
>
{
get
LowerPrice
(
detail
)
}
</
span
>
<
span
className=
"num"
>
{
get
SpecPrice
(
)
}
</
span
>
</>
)
:
(
<
span
className=
"label"
>
咨询报价
</
span
>
)
}
</
div
>
</
div
>
<
div
className=
"content-item flex-start"
>
<
div
className=
"content-item flex-start
align-start
"
>
<
div
className=
"item-label"
>
送至
</
div
>
<
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
className=
"content-item flex-start"
>
<
div
className=
"item-label"
>
规格
</
div
>
<
div
className=
"item-content"
>
共
{
detail
?.
priceStock
?.
length
||
0
}
种可选
共
{
detail
?.
priceStock
?.
length
||
0
}
种可选
</
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=
"item-label"
>
数量
</
div
>
<
div
className=
"item-content"
>
...
...
@@ -83,7 +181,7 @@ export default ProductHeadView;
const
ProductHeadWrap
=
styled
.
div
`
position: relative;
width: calc((100% - 0.83rem) / 10 * 7.5);
height: 28rem;
min-
height: 28rem;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
box-sizing: border-box;
...
...
@@ -91,18 +189,19 @@ const ProductHeadWrap = styled.div`
display: flex;
align-items: center;
justify-content: flex-start;
padding: 1rem;
padding:
0
1rem;
.product-swiper {
position: relative;
width: 22rem;
height:
100%
;
height:
26rem
;
box-sizing: border-box;
}
.product-content {
position: relative;
width: calc(100% - 22rem);
height: 100%;
box-sizing: border-box;
padding
-left:
1rem;
padding
: 1rem 0 1rem
1rem;
//background: lightblue;
.content-title {
width: 100%;
...
...
@@ -128,16 +227,18 @@ const ProductHeadWrap = styled.div`
box-sizing: border-box;
padding: 0.8rem;
margin-bottom: 1rem;
align-items: baseline;
.price-label {
width: 2rem;
color: #999999;
text-align: justify;
text-align-last: justify;
margin-right: 1rem;
transform: translateY(-0.1rem);
}
.price-money {
font-size: 24px;
line-height: 1;
//
line-height: 1;
color: #ff6700;
.label {
font-size: 18px;
...
...
@@ -154,8 +255,46 @@ const ProductHeadWrap = styled.div`
text-align-last: justify;
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 {
//position: absolute;
//left: 1rem;
//bottom: 0;
position: relative;
width: 100%;
margin-top: 2rem;
...
...
src/components/product-comp/product-store/index.tsx
浏览文件 @
201a32b5
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
{
InterDataType
}
from
'@/api/interface'
;
import
{
AppMallGoodsDetails
}
from
'@/api/interface/mall'
;
import
{
AppMallGoodsDetails
,
GetCompanyInfoByBUId
,
}
from
'@/api/interface/mall'
;
// 商品详情类型
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
`
position: relative;
width: calc((100% - 0.83rem) / 10 * 2.5);
height: 28rem;
min-
height: 28rem;
background: #ffffff;
border: 0.04rem solid #e3e3e3;
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
;
src/pages/mall/product/[id].tsx
浏览文件 @
201a32b5
...
...
@@ -30,7 +30,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const
res
=
await
MallAPI
.
appMallGoodsDetails
({
id
});
if
(
res
&&
res
.
code
===
'200'
)
{
productDetail
=
res
.
result
;
console
.
log
(
'获取商品详情 --->'
,
res
);
//
console.log('获取商品详情 --->', res);
}
};
// 获取店铺详情
...
...
@@ -40,7 +40,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
});
if
(
res
&&
res
.
code
===
'200'
)
{
storeDetail
=
res
.
result
;
console
.
log
(
'获取店铺详情 --->'
,
res
);
//
console.log('获取店铺详情 --->', res);
}
};
// 依次获取接口数据
...
...
@@ -63,9 +63,15 @@ const MallProductView: React.FC<{
<
BreadcrumbView
/>
<
div
className=
"flex-start"
>
<
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
>
{
productDetail
?.
tradeName
}
</
ProductWrap
>
</
LayoutView
>
);
...
...
src/pages/mall/product/styled.tsx
浏览文件 @
201a32b5
...
...
@@ -11,4 +11,36 @@ export const ProductWrap = styled.div`
box-sizing: border-box;
padding: 2rem 0 0 0;
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%;
}
}
}
`
;
src/styles/globals.css
浏览文件 @
201a32b5
...
...
@@ -164,7 +164,19 @@ a {
-webkit-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
{
background-color
:
transparent
;
-webkit-border-radius
:
2em
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论