@ -1,138 +0,0 @@ |
|||
import React, { useEffect, useState } from "react"; |
|||
import "./Activities.scss"; |
|||
import back from "./back.svg"; |
|||
import clock from "./clock.svg"; |
|||
import detail from "./detail.png"; |
|||
import { post } from "../../js/helpers/data-helper"; |
|||
|
|||
const Activities = ({ |
|||
initActivity, |
|||
setInitActivity, |
|||
onGo, |
|||
mall: { activities, pois }, |
|||
facilities, |
|||
memberID, |
|||
}) => { |
|||
const [activity, setActivity] = useState(null); |
|||
const [receiving, setReceiving] = useState(false); |
|||
const poiMap = pois.reduce((acc, nxt) => ({ ...acc, [nxt.code]: nxt }), {}); |
|||
const getFacByAct = (activity) => { |
|||
if (!activity) return null; |
|||
const poiCode = activity.poiList.find((code) => poiMap[code]); |
|||
const poi = poiCode ? poiMap[poiCode] : null; |
|||
if (!poi) return null; |
|||
const actFacs = facilities ? facilities["活动"] : []; |
|||
if (!actFacs) return null; |
|||
const actFac = actFacs.find(({ title }) => title === poi.name); |
|||
return actFac; |
|||
}; |
|||
|
|||
const reject = () => { |
|||
window.weui.toast("该活动即将上线,敬请期待!", { |
|||
className: "toast", |
|||
}); |
|||
}; |
|||
const receive = async (activity) => { |
|||
setReceiving(true); |
|||
try { |
|||
const { code, data, msg } = await post("/Api/Coupon/CouponReceive", { |
|||
activityCode: activity.code, |
|||
memberID, |
|||
}); |
|||
window.weui.toast(msg, { |
|||
className: "toast", |
|||
}); |
|||
if (code === "200") { |
|||
activity.isReceived = true; |
|||
} |
|||
} catch (error) { |
|||
console.warn(error); |
|||
} finally { |
|||
setReceiving(false); |
|||
} |
|||
}; |
|||
useEffect(() => { |
|||
if (initActivity) { |
|||
setActivity(initActivity); |
|||
setInitActivity(null); |
|||
} |
|||
}, [initActivity]); |
|||
|
|||
return ( |
|||
<div className="activities"> |
|||
<div className="header">活动</div> |
|||
<div className="list-container"> |
|||
{activities && |
|||
activities.map((act) => ( |
|||
<div key={act.code} className="card"> |
|||
<img className="card-header" src={act.fileUrl}></img> |
|||
<div className="card-content"> |
|||
<div className="detail" onClick={() => setActivity(act)}> |
|||
<img src={detail}></img>查看详情 |
|||
</div> |
|||
<div |
|||
className="btn" |
|||
onClick={() => { |
|||
const fac = getFacByAct(act); |
|||
if (!fac) return reject(); |
|||
return onGo && onGo(fac); |
|||
}} |
|||
> |
|||
GO |
|||
</div> |
|||
</div> |
|||
</div> |
|||
))} |
|||
</div> |
|||
{activity && ( |
|||
<div className="activity"> |
|||
<img |
|||
className="back" |
|||
onClick={() => setActivity(null)} |
|||
src={back} |
|||
></img> |
|||
<img className="r1" src={activity.fileUrl}></img> |
|||
<div className="r2"> |
|||
<div className="rr1">{activity.name}</div> |
|||
<div className="rr2"> |
|||
<img src={clock}></img> |
|||
{activity.beginTime.split(" ")[0]}至 |
|||
{activity.endTime.split(" ")[0]} |
|||
</div> |
|||
<div className="r3"> |
|||
<div className="title">活动介绍</div> |
|||
{activity.intro} |
|||
<div |
|||
className="r4" |
|||
onClick={() => { |
|||
const fac = getFacByAct(activity); |
|||
if (!fac) return reject(); |
|||
return onGo && onGo(fac); |
|||
}} |
|||
> |
|||
开始导航 |
|||
</div> |
|||
{activity.defaultAwardTitle && ( |
|||
<div className="r5"> |
|||
<div className="t1">{activity.defaultAwardTitle}</div> |
|||
<div className="meta">活动奖励</div> |
|||
<div |
|||
onClick={() => |
|||
!activity.isReceived && !receiving && receive(activity) |
|||
} |
|||
className={`btn ${ |
|||
activity.isReceived || receiving ? "disabled" : "" |
|||
}`}
|
|||
> |
|||
领取奖励 |
|||
</div> |
|||
</div> |
|||
)} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
export default Activities; |
|||
@ -1,264 +0,0 @@ |
|||
.activities { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
background: #f0f0f0; |
|||
|
|||
pointer-events: auto; |
|||
.header { |
|||
position: absolute; |
|||
height: 25px; |
|||
left: 24px; |
|||
top: 24px; |
|||
font-family: "PingFang SC"; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 18px; |
|||
line-height: 25px; |
|||
display: flex; |
|||
align-items: center; |
|||
color: #353230; |
|||
} |
|||
.list-container { |
|||
position: absolute; |
|||
top: 64px; |
|||
left: 17px; |
|||
right: 17px; |
|||
bottom: 0; |
|||
overflow-x: hidden; |
|||
overflow-y: auto; |
|||
.card { |
|||
display: inline-flex; |
|||
flex-direction: column; |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
overflow: hidden; |
|||
.card-header { |
|||
width: calc(100vw - 34px); |
|||
height: calc((100vw - 34px) / 321 * 180); |
|||
border-radius: 10px; |
|||
object-fit: cover; |
|||
} |
|||
.card-content { |
|||
display: flex; |
|||
height: 72px; |
|||
padding-left: 24px; |
|||
padding-right: 16px; |
|||
font-family: "PingFang SC"; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
color: #353230; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
.detail { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
img { |
|||
width: 20px; |
|||
height: 20px; |
|||
margin-right: 8px; |
|||
} |
|||
} |
|||
.btn { |
|||
display: flex; |
|||
width: 80px; |
|||
height: 40px; |
|||
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%); |
|||
box-shadow: 0px 6px 12px rgba(93, 172, 249, 0.2); |
|||
border-radius: 8px; |
|||
font-family: "HarmonyOS Sans SC"; |
|||
font-style: normal; |
|||
font-weight: 700; |
|||
font-size: 16px; |
|||
line-height: 19px; |
|||
text-align: right; |
|||
color: #ffffff; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
} |
|||
} |
|||
.card + .card { |
|||
margin-top: 16px; |
|||
} |
|||
} |
|||
.activity { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: -98px; |
|||
left: 0; |
|||
right: 0; |
|||
background: #f0f0f0; |
|||
z-index: 1; |
|||
border-radius: 18px; |
|||
.back { |
|||
position: absolute; |
|||
width: 40px; |
|||
height: 40px; |
|||
left: 16px; |
|||
top: 8px; |
|||
z-index: 2; |
|||
} |
|||
.r1 { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
height: 56vw; |
|||
width: 100vw; |
|||
z-index: 1; |
|||
border-radius: 10px 10px 0 0; |
|||
object-fit: cover; |
|||
} |
|||
.r2 { |
|||
position: absolute; |
|||
top: 192px; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
z-index: 2; |
|||
background: linear-gradient( |
|||
180.18deg, |
|||
#ffffff 17.3%, |
|||
rgba(255, 255, 255, 0) 99.84% |
|||
); |
|||
border-radius: 16px 16px 0 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
.rr1 { |
|||
font-family: "PingFang SC"; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
margin-top: 16px; |
|||
margin-bottom: 8px; |
|||
color: #353230; |
|||
padding-left: 24px; |
|||
} |
|||
.rr2 { |
|||
display: inline-flex; |
|||
font-family: "PingFang SC"; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
font-size: 12px; |
|||
line-height: 17px; |
|||
color: #68655e; |
|||
align-items: center; |
|||
padding-left: 24px; |
|||
img { |
|||
width: 16px; |
|||
height: 16px; |
|||
margin-right: 8px; |
|||
} |
|||
} |
|||
.r3 { |
|||
position: absolute; |
|||
top: 97px; |
|||
left: 10px; |
|||
right: 10px; |
|||
bottom: 0; |
|||
font-family: "PingFang SC"; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
font-size: 14px; |
|||
line-height: 22px; |
|||
color: #9d988f; |
|||
overflow-x: hidden; |
|||
overflow-y: auto; |
|||
background: #f9f9fb; |
|||
border-radius: 16px 16px 0px 0px; |
|||
padding: 16px 14px; |
|||
.title { |
|||
font-family: "PingFang SC"; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 14px; |
|||
line-height: 20px; |
|||
color: #353230; |
|||
margin-bottom: 4px; |
|||
} |
|||
} |
|||
.r4 { |
|||
display: flex; |
|||
position: absolute; |
|||
height: 56px; |
|||
left: 18px; |
|||
right: 18px; |
|||
bottom: 56px; |
|||
margin: auto; |
|||
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%); |
|||
box-shadow: 0px 6px 12px rgba(93, 172, 249, 0.2); |
|||
border-radius: 16px; |
|||
font-family: "HarmonyOS Sans SC"; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: #ffffff; |
|||
} |
|||
.r5 { |
|||
position: absolute; |
|||
bottom: 139px; |
|||
left: 14px; |
|||
right: 14px; |
|||
height: 80px; |
|||
background: rgba(255, 255, 255, 0.7); |
|||
box-shadow: 0px 12px 16px rgba(104, 110, 127, 0.08); |
|||
border-radius: 10px; |
|||
.t1 { |
|||
position: absolute; |
|||
top: 19px; |
|||
left: 20px; |
|||
font-weight: 600; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #437af7; |
|||
width: 174px; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} |
|||
.meta { |
|||
position: absolute; |
|||
top: 45px; |
|||
left: 20px; |
|||
font-weight: 600; |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
color: #a1a5b3; |
|||
} |
|||
.btn { |
|||
position: absolute; |
|||
display: flex; |
|||
width: 80px; |
|||
height: 40px; |
|||
top: 19px; |
|||
right: 12px; |
|||
background: #ffffff; |
|||
box-shadow: 0px 12px 16px rgba(104, 110, 127, 0.08); |
|||
border-radius: 10px; |
|||
font-weight: 600; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #437af7; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border: 1px solid rgba(80, 138, 247, 0.6); |
|||
&.disabled { |
|||
color: #a1a5b3; |
|||
border: 1px solid #a1a5b3; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 559 B |
|
Before Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,102 +0,0 @@ |
|||
import "./ApplyModal.scss"; |
|||
import Modal from "react-modal"; |
|||
import { useState } from "react"; |
|||
import { post } from "../../../js/helpers/data-helper"; |
|||
|
|||
const ApplyModal = ({ onBack, memberID }) => { |
|||
const [title, setTitle] = useState(""); |
|||
const [totalCount, setTotalCount] = useState(""); |
|||
const [beginTime, setBeginTime] = useState(""); |
|||
const [endTime, setEndTime] = useState(""); |
|||
const [couponRules, setCouponRules] = useState(""); |
|||
const toast = (text) => |
|||
window.weui.toast(text, { |
|||
className: "toast", |
|||
}); |
|||
const submit = async () => { |
|||
if (!title) return toast("请输入礼券标题"); |
|||
if (!totalCount) return toast("请输入发放数量"); |
|||
if (!beginTime) return toast("请选择开始时间"); |
|||
if (!endTime) return toast("请选择结束时间"); |
|||
try { |
|||
const { code, msg } = await post("/api/coupon/ApplyCoupon", { |
|||
title, |
|||
totalCount, |
|||
beginTime, |
|||
endTime, |
|||
memberID, |
|||
couponRules, |
|||
}); |
|||
if (code === "200") { |
|||
toast("已提交申请,审核中!"); |
|||
onBack && onBack(); |
|||
} else { |
|||
toast(msg); |
|||
} |
|||
} catch (error) {} |
|||
}; |
|||
return ( |
|||
<Modal isOpen ariaHideApp={false} className="ApplyModal"> |
|||
<div className="back" onClick={() => onBack && onBack()}></div> |
|||
<div className="title">申请发券</div> |
|||
<div className="content"> |
|||
<div className="item"> |
|||
<div className="label">礼券标题</div> |
|||
<input |
|||
type="text" |
|||
className="inputText" |
|||
placeholder="请输入礼券标题" |
|||
maxLength={30} |
|||
value={title} |
|||
onChange={(e) => setTitle(e.target.value)} |
|||
></input> |
|||
</div> |
|||
<div className="item"> |
|||
<div className="label">发放数量</div> |
|||
<input |
|||
type="number" |
|||
className="inputText" |
|||
placeholder="请输入发放数量" |
|||
value={totalCount} |
|||
min={1} |
|||
max={9999} |
|||
onChange={(e) => setTotalCount(e.target.value)} |
|||
></input> |
|||
</div> |
|||
<div className="item"> |
|||
<div className="label">使用期限</div> |
|||
<div className="dates"> |
|||
<input |
|||
type="date" |
|||
value={beginTime} |
|||
onChange={(e) => setBeginTime(e.target.value)} |
|||
placeholder="请选择开始时间" |
|||
></input> |
|||
<div style={{ margin: "0 15px" }}>至</div> |
|||
<input |
|||
type="date" |
|||
value={endTime} |
|||
onChange={(e) => setEndTime(e.target.value)} |
|||
placeholder="请选择结束时间" |
|||
></input> |
|||
</div> |
|||
</div> |
|||
<div className="item"> |
|||
<div className="label">礼券规则</div> |
|||
<textarea |
|||
type="text" |
|||
className="inputTextArea" |
|||
placeholder="请输入礼券规则" |
|||
value={couponRules} |
|||
maxLength={100} |
|||
onChange={(e) => setCouponRules(e.target.value)} |
|||
></textarea> |
|||
</div> |
|||
<div className="btn" onClick={submit}> |
|||
免费申请 |
|||
</div> |
|||
</div> |
|||
</Modal> |
|||
); |
|||
}; |
|||
export default ApplyModal; |
|||
@ -1,118 +0,0 @@ |
|||
.ApplyModal { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background: #f6f6f6; |
|||
overflow: hidden; |
|||
.back { |
|||
position: absolute; |
|||
top: 10px; |
|||
left: 10px; |
|||
width: 60px; |
|||
height: 60px; |
|||
background: center / cover no-repeat url(./back.svg); |
|||
} |
|||
.title { |
|||
position: absolute; |
|||
top: 28px; |
|||
right: 16px; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 20px; |
|||
line-height: 25px; |
|||
color: #000000; |
|||
} |
|||
.content { |
|||
position: absolute; |
|||
top: 92px; |
|||
left: 10px; |
|||
right: 10px; |
|||
bottom: 34px; |
|||
background: #ffffff; |
|||
border-radius: 18px; |
|||
padding: 32px 24px; |
|||
.item { |
|||
height: 58px; |
|||
border-bottom: 1px solid rgba(218, 215, 209, 0.4); |
|||
.label { |
|||
font-weight: 600; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #353230; |
|||
margin-bottom: 8px; |
|||
} |
|||
.inputText { |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #68655e; |
|||
border: none; |
|||
outline: none; |
|||
opacity: 0.8; |
|||
&::placeholder { |
|||
color: #dad7d1; |
|||
} |
|||
} |
|||
.inputTextArea { |
|||
width: 100%; |
|||
background: #f9f9fb; |
|||
border-radius: 8px; |
|||
height: 152px; |
|||
padding: 16px; |
|||
border: none; |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
outline: none; |
|||
color: #68655e; |
|||
&::placeholder { |
|||
color: #dad7d1; |
|||
} |
|||
} |
|||
.dates { |
|||
display: flex; |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #353230; |
|||
|
|||
input { |
|||
flex: 1; |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #68655e; |
|||
border: none; |
|||
outline: none; |
|||
opacity: 0.8; |
|||
overflow: hidden; |
|||
&::placeholder { |
|||
color: #dad7d1; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.item + .item { |
|||
margin-top: 24px; |
|||
} |
|||
.btn { |
|||
position: absolute; |
|||
left: 18px; |
|||
right: 18px; |
|||
bottom: 18px; |
|||
height: 56px; |
|||
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%); |
|||
box-shadow: 0px 6px 12px rgba(93, 172, 249, 0.2); |
|||
border-radius: 10px; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
color: #ffffff; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 544 B |
@ -1,163 +0,0 @@ |
|||
import "./CouponList.scss"; |
|||
import { useState, useRef, useEffect, useCallback } from "react"; |
|||
import ListEnd from "../ListEnd/ListEnd"; |
|||
import noData from "./noData.png"; |
|||
import Qrcode from "../Qrcode/Qrcode"; |
|||
import Modal from "react-modal"; |
|||
import { post } from "../../../js/helpers/data-helper"; |
|||
import InfiniteScroll from "react-infinite-scroller"; |
|||
|
|||
const stateMap = { |
|||
0: "未使用", |
|||
1: "已使用", |
|||
2: "已失效", |
|||
}; |
|||
const stateList = [0, 1, 2]; |
|||
const CouponList = ({ memberID }) => { |
|||
const [state, setState] = useState(stateList[0]); |
|||
const [list, setList] = useState([]); |
|||
const [nextPageIndex, setNextPageIndex] = useState(1); |
|||
const [loading, setLoading] = useState(false); |
|||
const hasMore = nextPageIndex !== null; |
|||
|
|||
const [modalCoupon, setModalCoupon] = useState(null); |
|||
const listRef = useRef(); |
|||
const showNoData = list !== null && list.length === 0; |
|||
const showListEnd = list !== null && list.length > 0; |
|||
const showCouponModal = !!modalCoupon; |
|||
|
|||
const loadMore = useCallback(async () => { |
|||
if (loading || nextPageIndex === null || !memberID) return; |
|||
setLoading(true); |
|||
try { |
|||
const { code, data, msg } = await post("/api/coupon/UserCouponList", { |
|||
paging: 1, |
|||
state, |
|||
pageIndex: nextPageIndex, |
|||
pageSize: 10, |
|||
memberID, |
|||
}); |
|||
if (code === "200") { |
|||
setList([...list, ...data.list]); |
|||
setNextPageIndex( |
|||
nextPageIndex + 1 > data.allPage ? null : nextPageIndex + 1 |
|||
); |
|||
} else { |
|||
setNextPageIndex(null); |
|||
window.weui.toast(msg, { |
|||
className: "toast", |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
setNextPageIndex(null); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}, [list, loading, nextPageIndex]); |
|||
|
|||
useEffect(() => { |
|||
if (listRef.current) listRef.current.scrollTop = 0; |
|||
setNextPageIndex(1); |
|||
setList([]); |
|||
}, [state]); |
|||
|
|||
return ( |
|||
<div className="CouponList"> |
|||
<div className="CouponTabs"> |
|||
{stateList.map((item) => ( |
|||
<div |
|||
key={item} |
|||
className={`tab ${state === item ? "active" : ""}`} |
|||
onClick={() => setState(item)} |
|||
> |
|||
{stateMap[item]} |
|||
</div> |
|||
))} |
|||
</div> |
|||
<div className="list-container" ref={listRef}> |
|||
<InfiniteScroll |
|||
pageStart={0} |
|||
loadMore={loadMore} |
|||
hasMore={hasMore} |
|||
useWindow={false} |
|||
getScrollParent={() => listRef && listRef.current} |
|||
> |
|||
{showNoData && ( |
|||
<div className="noData"> |
|||
<img className="bg" src={noData}></img> |
|||
<div className="meta"> |
|||
<div className="btn" onClick={() => {}}> |
|||
参加AR |
|||
</div> |
|||
活动,可领取优惠券! |
|||
</div> |
|||
</div> |
|||
)} |
|||
{list !== null && |
|||
list.map((coupon) => ( |
|||
<div |
|||
key={coupon.orderNo} |
|||
className={`coupon bg${state}`} |
|||
onClick={() => setModalCoupon(coupon)} |
|||
> |
|||
{coupon.logoUrl ? ( |
|||
<img |
|||
className="logo" |
|||
src={coupon.logoUrl} |
|||
style={{ |
|||
width: (window.innerWidth / 375) * 64, |
|||
height: (window.innerWidth / 375) * 64, |
|||
}} |
|||
></img> |
|||
) : ( |
|||
<Qrcode |
|||
className="qrcode" |
|||
size={(window.innerWidth / 375) * 64} |
|||
text={coupon.orderNo} |
|||
></Qrcode> |
|||
)} |
|||
<div className="title">{coupon.title}</div> |
|||
<div className="meta">{coupon.showNo}</div> |
|||
<div className="duration"> |
|||
使用期限<div className="sep"></div> |
|||
{coupon.beginTime}至{coupon.endTime} |
|||
</div> |
|||
</div> |
|||
))} |
|||
|
|||
{showListEnd && <ListEnd></ListEnd>} |
|||
</InfiniteScroll> |
|||
</div> |
|||
{showCouponModal && ( |
|||
<Modal |
|||
isOpen |
|||
ariaHideApp={false} |
|||
style={{ overlay: { background: "rgba(0, 0, 0, 0.6)" } }} |
|||
className="CouponModal" |
|||
> |
|||
<div className="close" onClick={() => setModalCoupon(null)}></div> |
|||
<Qrcode |
|||
className="qrcode" |
|||
size={120} |
|||
text={modalCoupon.orderNo} |
|||
></Qrcode> |
|||
<div className="content"> |
|||
<div className="title">{modalCoupon.title}</div> |
|||
<div className="coupon-code">{modalCoupon.showNo}</div> |
|||
<div className="meta">使用期限</div> |
|||
<div className="value"> |
|||
{modalCoupon.beginTime}至{modalCoupon.endTime} |
|||
</div> |
|||
{modalCoupon.couponRules && ( |
|||
<> |
|||
<div className="meta">礼券规则</div> |
|||
<div className="value">{modalCoupon.couponRules}</div> |
|||
</> |
|||
)} |
|||
</div> |
|||
</Modal> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
export default CouponList; |
|||
@ -1,268 +0,0 @@ |
|||
.CouponList { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
background: linear-gradient( |
|||
180deg, |
|||
#f2f2f2 0%, |
|||
rgba(249, 249, 249, 0) 20.18% |
|||
); |
|||
border-radius: 18px 18px 0 0; |
|||
padding-top: 10px; |
|||
width: 100vw; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
.CouponTabs { |
|||
display: flex; |
|||
flex: 0 0 56px; |
|||
width: calc(100vw - 20px); |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
|
|||
.tab { |
|||
position: relative; |
|||
display: flex; |
|||
flex: 1; |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #9d988f; |
|||
justify-content: center; |
|||
align-items: center; |
|||
&.active { |
|||
color: #508af7; |
|||
&::before { |
|||
content: ""; |
|||
position: absolute; |
|||
bottom: 10px; |
|||
left: 0; |
|||
right: 0; |
|||
margin: auto; |
|||
width: 4px; |
|||
height: 4px; |
|||
background: #508af7; |
|||
border-radius: 50%; |
|||
} |
|||
&::after { |
|||
content: ""; |
|||
position: absolute; |
|||
width: 30px; |
|||
height: 6px; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
margin: auto; |
|||
background: center / cover no-repeat url(./tri.svg); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.list-container { |
|||
flex: 1; |
|||
overflow-x: hidden; |
|||
overflow-y: scroll; |
|||
padding-top: 16px; |
|||
text-align: center; |
|||
scroll-behavior: smooth; |
|||
|
|||
&::-webkit-scrollbar { |
|||
display: none; |
|||
} |
|||
.coupon { |
|||
position: relative; |
|||
width: calc(100vw - 20px); |
|||
height: calc((100vw - 20px) / 355 * 134); |
|||
&.bg0 { |
|||
background: center / cover no-repeat url(./bg0.svg); |
|||
} |
|||
&.bg1 { |
|||
background: center / cover no-repeat url(./bg1.svg); |
|||
} |
|||
&.bg2 { |
|||
background: center / cover no-repeat url(./bg2.svg); |
|||
} |
|||
.logo { |
|||
position: absolute; |
|||
top: 5.333vw; |
|||
left: 5.333vw; |
|||
border-radius: 4px; |
|||
object-fit: cover; |
|||
} |
|||
.title { |
|||
position: absolute; |
|||
top: 8vw; |
|||
left: 27.733vw; |
|||
right: 2.667vw; |
|||
font-weight: 600; |
|||
font-size: 4.533vw; |
|||
line-height: 5.867vw; |
|||
color: #353230; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
text-align: left; |
|||
} |
|||
.meta { |
|||
position: absolute; |
|||
top: 14.933vw; |
|||
left: 27.733vw; |
|||
font-weight: 600; |
|||
font-size: 3.467vw; |
|||
line-height: 4.8vw; |
|||
color: #68655e; |
|||
opacity: 0.8; |
|||
} |
|||
.duration { |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
height: 9.067vw; |
|||
font-weight: 400; |
|||
font-size: 3.2vw; |
|||
line-height: 4.267vw; |
|||
text-align: center; |
|||
color: #9d988f; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
.sep { |
|||
width: 0.267vw; |
|||
height: 2.667vw; |
|||
opacity: 0.3; |
|||
border-left: 0.267vw solid #68655e; |
|||
margin-left: 3.2vw; |
|||
margin-right: 3.2vw; |
|||
} |
|||
} |
|||
.time-left { |
|||
position: absolute; |
|||
height: 3.467vw; |
|||
top: 2.133vw; |
|||
right: 1.067vw; |
|||
font-weight: 600; |
|||
font-size: 2.933vw; |
|||
line-height: 3.467vw; |
|||
color: #c1a672; |
|||
opacity: 0.8; |
|||
} |
|||
.qrcode { |
|||
position: absolute; |
|||
top: 5.333vw; |
|||
left: 5.333vw; |
|||
} |
|||
} |
|||
.coupon + .coupon { |
|||
margin-top: 8px; |
|||
} |
|||
.noData { |
|||
display: flex; |
|||
flex-direction: column; |
|||
width: calc(100vw - 20px); |
|||
height: 396px; |
|||
border-radius: 18px; |
|||
background: #ffffff; |
|||
align-items: center; |
|||
.bg { |
|||
margin: auto; |
|||
width: 250px; |
|||
height: 250px; |
|||
margin-top: 16px; |
|||
} |
|||
.meta { |
|||
width: calc(100vw - 46px); |
|||
flex: 0 0 90px; |
|||
border-top: 1px solid #f5f5f5; |
|||
display: flex; |
|||
font-weight: 400; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #9d988f; |
|||
height: 90px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
.btn { |
|||
color: #508af7; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.CouponModal { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
width: 326px; |
|||
height: 416px; |
|||
margin: auto; |
|||
background: #f9f9fb; |
|||
border-radius: 16px; |
|||
.close { |
|||
position: absolute; |
|||
top: 16px; |
|||
right: 16px; |
|||
width: 24px; |
|||
height: 24px; |
|||
background: center / cover no-repeat url(./close.svg); |
|||
} |
|||
.qrcode { |
|||
position: absolute; |
|||
top: 52px; |
|||
left: 0; |
|||
right: 0; |
|||
margin: auto; |
|||
} |
|||
.content { |
|||
position: absolute; |
|||
bottom: 8px; |
|||
left: 8px; |
|||
right: 8px; |
|||
background: #f3f4f8; |
|||
border-radius: 10px; |
|||
height: 192px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: flex-start; |
|||
flex-direction: column; |
|||
padding: 24px 16px 0 16px; |
|||
.title { |
|||
font-weight: 600; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #323337; |
|||
} |
|||
.coupon-code { |
|||
font-weight: 600; |
|||
font-size: 13px; |
|||
line-height: 18px; |
|||
color: #437af7; |
|||
opacity: 0.8; |
|||
margin-top: 4px; |
|||
padding-bottom: 8px; |
|||
border-bottom: 1px dashed #c9cbd1; |
|||
margin-bottom: 17px; |
|||
width: 100%; |
|||
} |
|||
.meta { |
|||
font-weight: 400; |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
color: #7a7e8d; |
|||
opacity: 0.8; |
|||
margin-bottom: 2px; |
|||
} |
|||
.value { |
|||
font-weight: 600; |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
color: #474a56; |
|||
opacity: 0.8; |
|||
margin-bottom: 8px; |
|||
min-height: 16px; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 313 B |
@ -1,55 +0,0 @@ |
|||
import "./Coupons.scss"; |
|||
import notMember from "./notMember.png"; |
|||
import Modal from "react-modal"; |
|||
import { useState } from "react"; |
|||
import ShopTabs from "./ShopTabs/ShopTabs"; |
|||
import CouponList from "./CouponList/CouponList"; |
|||
import ShopManager from "./ShopManager/ShopManager"; |
|||
|
|||
const Coupons = ({ show, memberID, isShop = false }) => { |
|||
const isNotMember = !memberID; |
|||
const [showCoupons, setShowCoupons] = useState(true); |
|||
return ( |
|||
<Modal |
|||
isOpen={show} |
|||
ariaHideApp={false} |
|||
style={{ overlay: { background: "transparent", pointerEvents: "none" } }} |
|||
className="Coupons" |
|||
> |
|||
{isNotMember ? ( |
|||
<div className="notMember"> |
|||
<img className="bg" src={notMember}></img> |
|||
<div className="meta"> |
|||
<div |
|||
className="btn" |
|||
onClick={() => { |
|||
window.wx.miniProgram.redirectTo({ |
|||
url: `/pages/login/index`, |
|||
}); |
|||
}} |
|||
> |
|||
注册/登录 |
|||
</div> |
|||
后,可查看优惠卷 |
|||
</div> |
|||
</div> |
|||
) : ( |
|||
<> |
|||
{isShop && ( |
|||
<ShopTabs |
|||
showCoupons={showCoupons} |
|||
setShowCoupons={setShowCoupons} |
|||
></ShopTabs> |
|||
)} |
|||
{(!isShop || (isShop && showCoupons)) && ( |
|||
<CouponList memberID={memberID}></CouponList> |
|||
)} |
|||
{isShop && !showCoupons && ( |
|||
<ShopManager memberID={memberID}></ShopManager> |
|||
)} |
|||
</> |
|||
)} |
|||
</Modal> |
|||
); |
|||
}; |
|||
export default Coupons; |
|||
@ -1,69 +0,0 @@ |
|||
.Coupons { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 98px; |
|||
z-index: 100000; |
|||
background: #f6f6f6; |
|||
pointer-events: auto; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
.notMember { |
|||
display: flex; |
|||
flex-direction: column; |
|||
width: calc(100vw - 20px); |
|||
height: 396px; |
|||
border-radius: 18px; |
|||
background: #ffffff; |
|||
align-items: center; |
|||
.bg { |
|||
margin: auto; |
|||
width: 250px; |
|||
height: 250px; |
|||
margin-top: 16px; |
|||
} |
|||
.meta { |
|||
width: calc(100vw - 46px); |
|||
flex: 0 0 90px; |
|||
border-top: 1px solid #f5f5f5; |
|||
display: flex; |
|||
font-weight: 400; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #9d988f; |
|||
height: 90px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
.btn { |
|||
color: #437af7; |
|||
} |
|||
} |
|||
} |
|||
.shopTabs { |
|||
display: flex; |
|||
width: calc(100vw - 20px); |
|||
height: 56px; |
|||
background: #ffffff; |
|||
border-radius: 12px; |
|||
padding: 4px; |
|||
.tab { |
|||
display: flex; |
|||
flex: 1; |
|||
height: 48px; |
|||
font-weight: 500; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
color: #68655e; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border-radius: 8px; |
|||
&.active { |
|||
color: #ffffff; |
|||
background: #daba7f; |
|||
box-shadow: 0px 6px 12px rgba(214, 154, 66, 0.3); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +0,0 @@ |
|||
import bg from "./bg.svg"; |
|||
import "./ListEnd.scss"; |
|||
const ListEnd = () => <img className="ListEnd" src={bg}></img>; |
|||
export default ListEnd; |
|||
@ -1,5 +0,0 @@ |
|||
.ListEnd { |
|||
margin-top: 24px; |
|||
width: 62px; |
|||
height: 40px; |
|||
} |
|||
|
Before Width: | Height: | Size: 4.4 KiB |
@ -1,25 +0,0 @@ |
|||
import QRCode from "qrcodejs2"; |
|||
import { useRef, useEffect } from "react"; |
|||
const Qrcode = ({ size, text, className }) => { |
|||
const ref = useRef(); |
|||
|
|||
useEffect(() => { |
|||
const domEl = ref.current; |
|||
domEl.innerHTML = ""; |
|||
new QRCode(domEl, { |
|||
width: size, |
|||
height: size, |
|||
text, |
|||
colorDark: "#000", |
|||
colorLight: "#fff", |
|||
}); |
|||
}, [ref]); |
|||
return ( |
|||
<div |
|||
className={className} |
|||
ref={ref} |
|||
style={{ width: size + "px", height: size + "px" }} |
|||
></div> |
|||
); |
|||
}; |
|||
export default Qrcode; |
|||
@ -1,59 +0,0 @@ |
|||
import "./ScanModal.scss"; |
|||
import Modal from "react-modal"; |
|||
import { useState, useEffect } from "react"; |
|||
import { Html5Qrcode } from "html5-qrcode"; |
|||
|
|||
const ScanModal = ({ onBack, onCode }) => { |
|||
const [loaded, setLoaded] = useState(false); |
|||
|
|||
const toast = (text) => |
|||
window.weui.toast(text, { |
|||
className: "toast", |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
if (loaded) { |
|||
const dom = document.getElementById("qrcode-scaner"); |
|||
const html5QrCode = new Html5Qrcode("qrcode-scaner"); |
|||
html5QrCode |
|||
.start( |
|||
{ facingMode: "environment" }, |
|||
{ fps: 10, qrbox: { width: 280, height: 280 } }, |
|||
(code) => { |
|||
html5QrCode.stop(); |
|||
onCode && onCode(code); |
|||
} |
|||
) |
|||
.catch(() => { |
|||
toast("启动二维码扫描失败"); |
|||
onBack && onBack(); |
|||
}); |
|||
return () => { |
|||
html5QrCode.stop(); |
|||
}; |
|||
} |
|||
}, [loaded]); |
|||
return ( |
|||
<Modal |
|||
onAfterOpen={() => setLoaded(true)} |
|||
isOpen |
|||
ariaHideApp={false} |
|||
className="ScanModal" |
|||
> |
|||
<div className="border"> |
|||
<div className="scaner"></div> |
|||
<div |
|||
className="container" |
|||
id="qrcode-scaner" |
|||
width="280px" |
|||
height="280px" |
|||
></div> |
|||
</div> |
|||
<div className="btn" onClick={() => onBack && onBack()}> |
|||
手动输入券码 |
|||
</div> |
|||
<div className="back" onClick={() => onBack && onBack()}></div> |
|||
</Modal> |
|||
); |
|||
}; |
|||
export default ScanModal; |
|||
@ -1,72 +0,0 @@ |
|||
.ScanModal { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background: #353230; |
|||
overflow: hidden; |
|||
.back { |
|||
position: absolute; |
|||
bottom: 48px; |
|||
left: 0; |
|||
right: 0; |
|||
margin: auto; |
|||
width: 48px; |
|||
height: 48px; |
|||
background: center / cover no-repeat url(./close.svg); |
|||
} |
|||
.btn { |
|||
position: absolute; |
|||
height: 56px; |
|||
left: 29px; |
|||
right: 28px; |
|||
bottom: 130px; |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
color: #437af7; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.border { |
|||
position: absolute; |
|||
top: 94px; |
|||
left: 0; |
|||
right: 0; |
|||
margin: auto; |
|||
width: 320px; |
|||
height: 320px; |
|||
background: center / cover no-repeat url(./border.svg); |
|||
overflow: hidden; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
@keyframes scaner { |
|||
0% { |
|||
top: -74px; |
|||
} |
|||
100% { |
|||
top: 320px; |
|||
} |
|||
} |
|||
.scaner { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
width: 320px; |
|||
height: 74px; |
|||
background: center / cover no-repeat url(./scaner.svg); |
|||
animation: 1s scaner infinite ease-in-out; |
|||
z-index: 2; |
|||
} |
|||
.container { |
|||
width: 280px; |
|||
height: 280px; |
|||
overflow: hidden; |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 424 B |
@ -1,130 +0,0 @@ |
|||
import "./ShopManager.scss"; |
|||
import noDataPic from "./noDataPic.png"; |
|||
import { post } from "../../../js/helpers/data-helper"; |
|||
import InfiniteScroll from "react-infinite-scroller"; |
|||
import { useState, useRef, useCallback } from "react"; |
|||
import ListEnd from "../ListEnd/ListEnd"; |
|||
import ApplyModal from "../ApplyModal/ApplyModal"; |
|||
import WriteOffModal from "../WriteOffModal/WriteOffModal"; |
|||
|
|||
const stateMap = { |
|||
0: "待审核", |
|||
1: "已发布", |
|||
2: "已拒绝", |
|||
}; |
|||
|
|||
const ShopManager = ({ memberID }) => { |
|||
const [list, setList] = useState([]); |
|||
const [nextPageIndex, setNextPageIndex] = useState(1); |
|||
const [loading, setLoading] = useState(false); |
|||
|
|||
const [showApplyModal, setShowApplyModal] = useState(false); |
|||
const [showWriteOffModal, setShowWriteOffModal] = useState(false); |
|||
const listRef = useRef(); |
|||
const showNoData = !loading && list.length === 0; |
|||
const showListEnd = !loading && list.length > 0; |
|||
|
|||
const hasMore = nextPageIndex !== null; |
|||
|
|||
const loadMore = useCallback(async () => { |
|||
if (loading || nextPageIndex === null) return; |
|||
setLoading(true); |
|||
try { |
|||
const { code, data, msg } = await post("/api/coupon/ApplyedList", { |
|||
paging: 1, |
|||
pageIndex: nextPageIndex, |
|||
pageSize: 10, |
|||
memberID, |
|||
}); |
|||
if (code === "200") { |
|||
setList([...list, ...data.list]); |
|||
setNextPageIndex( |
|||
nextPageIndex + 1 > data.allPage ? null : nextPageIndex + 1 |
|||
); |
|||
} else { |
|||
setNextPageIndex(null); |
|||
window.weui.toast(msg, { |
|||
className: "toast", |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
setNextPageIndex(null); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}, [list, loading, nextPageIndex]); |
|||
return ( |
|||
<div className="ShopManager"> |
|||
<div className="btns"> |
|||
<div className="btn" onClick={() => setShowApplyModal(true)}> |
|||
申请 |
|||
</div> |
|||
<div className="btn" onClick={() => setShowWriteOffModal(true)}> |
|||
核销 |
|||
</div> |
|||
</div> |
|||
|
|||
<div className="list-container" ref={listRef}> |
|||
<InfiniteScroll |
|||
loadMore={loadMore} |
|||
hasMore={hasMore} |
|||
useWindow={false} |
|||
getScrollParent={() => listRef && listRef.current} |
|||
> |
|||
{showNoData && ( |
|||
<> |
|||
<img className="noDataPic" src={noDataPic}></img> |
|||
<div className="noDataText"> |
|||
当前您还未发布礼券,请先申请,等待后台申请通过后,刷新页面显示 |
|||
</div> |
|||
</> |
|||
)} |
|||
{list !== null && |
|||
list.map((coupon) => ( |
|||
<div key={coupon.code} className={`coupon status${coupon.state}`}> |
|||
<div className="row"> |
|||
<div className="r1">礼券标题</div> |
|||
<div className="r2">{coupon.title}</div> |
|||
</div> |
|||
<div className="row"> |
|||
<div className="r1">发放数量</div> |
|||
<div className="r2">{coupon.totalCount}</div> |
|||
</div> |
|||
{coupon.state === 1 && ( |
|||
<div className="row"> |
|||
<div className="r1">已领取数</div> |
|||
<div className="r2">{coupon.received}</div> |
|||
</div> |
|||
)} |
|||
<div className="row"> |
|||
<div className="r1">使用期限</div> |
|||
<div className="r2"> |
|||
{coupon.beginTime}至{coupon.endTime} |
|||
</div> |
|||
</div> |
|||
<div className="status">{stateMap[coupon.state]}</div> |
|||
</div> |
|||
))} |
|||
{showListEnd && <ListEnd></ListEnd>} |
|||
</InfiniteScroll> |
|||
</div> |
|||
{showApplyModal && ( |
|||
<ApplyModal |
|||
onBack={() => { |
|||
setShowApplyModal(false); |
|||
setList([]); |
|||
setNextPageIndex(1); |
|||
}} |
|||
memberID={memberID} |
|||
></ApplyModal> |
|||
)} |
|||
{showWriteOffModal && ( |
|||
<WriteOffModal |
|||
onBack={() => setShowWriteOffModal(false)} |
|||
memberID={memberID} |
|||
></WriteOffModal> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
export default ShopManager; |
|||
@ -1,132 +0,0 @@ |
|||
.ShopManager { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
background: linear-gradient( |
|||
180deg, |
|||
#f2f2f2 0%, |
|||
rgba(249, 249, 249, 0) 20.18% |
|||
); |
|||
border-radius: 18px 18px 0 0; |
|||
padding-top: 10px; |
|||
width: 100vw; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
.btns { |
|||
display: flex; |
|||
margin-bottom: 10px; |
|||
width: calc(100vw - 20px); |
|||
.btn { |
|||
display: flex; |
|||
flex: 1; |
|||
font-weight: 400; |
|||
font-size: 17px; |
|||
color: #353230; |
|||
height: 48px; |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.btn + .btn { |
|||
margin-left: 9px; |
|||
} |
|||
} |
|||
.noDataPic { |
|||
margin-top: 64px; |
|||
width: 250px; |
|||
height: 250px; |
|||
} |
|||
.noDataText { |
|||
margin: 0 43px; |
|||
font-weight: 400; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
text-align: center; |
|||
color: #68655e; |
|||
} |
|||
.list-container { |
|||
flex: 1; |
|||
overflow-x: hidden; |
|||
overflow-y: scroll; |
|||
padding-top: 16px; |
|||
text-align: center; |
|||
scroll-behavior: smooth; |
|||
|
|||
&::-webkit-scrollbar { |
|||
display: none; |
|||
} |
|||
.coupon { |
|||
position: relative; |
|||
width: calc(100vw - 20px); |
|||
border-radius: 16px; |
|||
overflow: hidden; |
|||
padding: 24px; |
|||
.row { |
|||
height: 50px; |
|||
border-bottom: 1px solid rgba(240, 240, 240, 0.4); |
|||
|
|||
.r1 { |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #9d988f; |
|||
text-align: left; |
|||
} |
|||
.r2 { |
|||
font-weight: 600; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #353230; |
|||
margin-top: 6px; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
text-align: left; |
|||
} |
|||
} |
|||
.row + .row { |
|||
margin-top: 10px; |
|||
} |
|||
.status { |
|||
position: absolute; |
|||
width: 72px; |
|||
height: 32px; |
|||
top: 0; |
|||
right: 0; |
|||
display: flex; |
|||
font-weight: 600; |
|||
font-size: 13px; |
|||
line-height: 18px; |
|||
color: #ffffff; |
|||
border-radius: 0px 0px 0px 14px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
&.status0 { |
|||
background: linear-gradient(241.87deg, #ffffff 2.89%, #fffbf8 97.29%); |
|||
.status { |
|||
background: linear-gradient(90deg, #ecd3a6 5.56%, #dbb979 100%); |
|||
box-shadow: 4px 6px 18px rgba(214, 162, 101, 0.3); |
|||
} |
|||
} |
|||
&.status1 { |
|||
background: linear-gradient(241.87deg, #ffffff 2.89%, #fefff8 97.29%); |
|||
.status { |
|||
background: linear-gradient(270.4deg, #71c956 0.27%, #7add83 87.87%); |
|||
box-shadow: 4px 6px 18px rgba(151, 214, 101, 0.3); |
|||
} |
|||
} |
|||
&.status2 { |
|||
background: linear-gradient(241.87deg, #ffffff 2.89%, #fffafa 97.29%); |
|||
.status { |
|||
background: linear-gradient(270deg, #e76464 3.47%, #ffa0a0 90.28%); |
|||
box-shadow: 4px 6px 18px rgba(214, 101, 122, 0.3); |
|||
} |
|||
} |
|||
} |
|||
.coupon + .coupon { |
|||
margin-top: 8px; |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 42 KiB |
@ -1,26 +0,0 @@ |
|||
import "./ShopTabs.scss"; |
|||
import card from "./card.svg"; |
|||
import cardActive from "./cardActive.svg"; |
|||
import ticket from "./ticket.svg"; |
|||
import ticketActive from "./ticketActive.svg"; |
|||
const ShopTabs = ({ showCoupons, setShowCoupons }) => { |
|||
return ( |
|||
<div className="ShopTabs"> |
|||
<div |
|||
className={`tab ${showCoupons ? "active" : ""}`} |
|||
onClick={() => setShowCoupons(true)} |
|||
> |
|||
<img src={showCoupons ? cardActive : card}></img> |
|||
我的礼包 |
|||
</div> |
|||
<div |
|||
className={`tab ${!showCoupons ? "active" : ""}`} |
|||
onClick={() => setShowCoupons(false)} |
|||
> |
|||
<img src={!showCoupons ? ticketActive : ticket}></img> |
|||
券管家 |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default ShopTabs; |
|||
@ -1,32 +0,0 @@ |
|||
.ShopTabs { |
|||
display: flex; |
|||
width: calc(100vw - 20px); |
|||
height: 56px; |
|||
background: #ffffff; |
|||
border-radius: 12px; |
|||
padding: 4px; |
|||
margin-bottom: 16px; |
|||
margin-top: 10px; |
|||
.tab { |
|||
display: flex; |
|||
flex: 1; |
|||
height: 48px; |
|||
font-weight: 500; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
color: #68655e; |
|||
justify-content: center; |
|||
align-items: center; |
|||
border-radius: 8px; |
|||
img { |
|||
width: 20px; |
|||
height: 20px; |
|||
margin-right: 8px; |
|||
} |
|||
&.active { |
|||
color: #ffffff; |
|||
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%); |
|||
box-shadow: 0px 6px 12px rgba(93, 172, 249, 0.2); |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 617 B |
|
Before Width: | Height: | Size: 615 B |
@ -1,148 +0,0 @@ |
|||
import "./WriteOffModal.scss"; |
|||
import Modal from "react-modal"; |
|||
import { useState, useRef, useEffect, useCallback } from "react"; |
|||
import { post } from "../../../js/helpers/data-helper"; |
|||
import ScanModal from "../ScanModal/ScanModal"; |
|||
import InfiniteScroll from "react-infinite-scroller"; |
|||
import ListEnd from "../ListEnd/ListEnd"; |
|||
|
|||
const WriteOffModal = ({ onBack, memberID }) => { |
|||
const [orderNo, setOrderNo] = useState(""); |
|||
const [showScanModal, setShowScanModal] = useState(false); |
|||
const [list, setList] = useState([]); |
|||
const [nextPageIndex, setNextPageIndex] = useState(1); |
|||
const [loading, setLoading] = useState(false); |
|||
const hasMore = nextPageIndex !== null; |
|||
const [allCount, setAllCount] = useState(0); |
|||
const [month, setMonth] = useState(new Date().toJSON().slice(0, 7)); |
|||
const showListEnd = list !== null && list.length > 0; |
|||
const listRef = useRef(); |
|||
|
|||
const loadMore = useCallback(async () => { |
|||
if (loading || nextPageIndex === null) return; |
|||
setLoading(true); |
|||
try { |
|||
const { code, data, msg } = await post("/api/Coupon/WriteOffHis", { |
|||
paging: 1, |
|||
pageIndex: nextPageIndex, |
|||
pageSize: 10, |
|||
memberID, |
|||
month, |
|||
}); |
|||
if (code === "200") { |
|||
setList([...list, ...data.list]); |
|||
setNextPageIndex( |
|||
nextPageIndex + 1 > data.allPage ? null : nextPageIndex + 1 |
|||
); |
|||
setAllCount(data.allCount); |
|||
} else { |
|||
setNextPageIndex(null); |
|||
window.weui.toast(msg, { |
|||
className: "toast", |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
setNextPageIndex(null); |
|||
} finally { |
|||
setLoading(false); |
|||
} |
|||
}, [list, loading, nextPageIndex]); |
|||
|
|||
useEffect(() => { |
|||
if (listRef.current) listRef.current.scrollTop = 0; |
|||
setNextPageIndex(1); |
|||
setAllCount(0); |
|||
setList([]); |
|||
}, [month]); |
|||
|
|||
const toast = (text) => |
|||
window.weui.toast(text, { |
|||
className: "toast", |
|||
}); |
|||
const writeOff = async (orderNo) => { |
|||
try { |
|||
const { code, msg } = await post("/api/coupon/writeoffcoupon", { |
|||
orderNo, |
|||
memberID, |
|||
}); |
|||
if (code === "200") { |
|||
toast("核销成功"); |
|||
return true; |
|||
} else { |
|||
toast(msg); |
|||
return false; |
|||
} |
|||
} catch (error) { |
|||
return false; |
|||
} |
|||
}; |
|||
const submitFromBtn = async (orderNo) => { |
|||
const result = await writeOff(orderNo); |
|||
if (result) { |
|||
setOrderNo(""); |
|||
} |
|||
}; |
|||
const handleCode = async (orderNo) => { |
|||
await writeOff(orderNo); |
|||
setShowScanModal(false); |
|||
}; |
|||
return ( |
|||
<Modal isOpen ariaHideApp={false} className="WriteOffModal"> |
|||
<div className="back" onClick={() => onBack && onBack()}></div> |
|||
<div className="title">核销</div> |
|||
<div className="search"> |
|||
<div className="icon"></div> |
|||
<input |
|||
type="number" |
|||
value={orderNo} |
|||
onChange={(e) => setOrderNo(e.target.value)} |
|||
></input> |
|||
</div> |
|||
<div className="scan" onClick={() => setShowScanModal(true)}></div> |
|||
{!!orderNo && ( |
|||
<div className="btn" onClick={() => submitFromBtn(orderNo)}> |
|||
确认核销 |
|||
</div> |
|||
)} |
|||
<div className="bar"> |
|||
<div>已核销({allCount}张)</div> |
|||
<input |
|||
type="month" |
|||
value={month} |
|||
onChange={(e) => setMonth(e.target.value)} |
|||
></input> |
|||
</div> |
|||
<div className="list-container" ref={listRef}> |
|||
<InfiniteScroll |
|||
pageStart={0} |
|||
loadMore={loadMore} |
|||
hasMore={hasMore} |
|||
useWindow={false} |
|||
getScrollParent={() => listRef && listRef.current} |
|||
> |
|||
{list !== null && |
|||
list.map((coupon) => ( |
|||
<div key={coupon.orderNo} className="coupon"> |
|||
<div className="block"> |
|||
<div className="r1">礼券标题</div> |
|||
<div className="r2">{coupon.title}</div> |
|||
</div> |
|||
<div className="block"> |
|||
<div className="r1">核销时间</div> |
|||
<div className="r2">{coupon.writeOffTime}</div> |
|||
</div> |
|||
</div> |
|||
))} |
|||
{showListEnd && <ListEnd></ListEnd>} |
|||
</InfiniteScroll> |
|||
</div> |
|||
{showScanModal && ( |
|||
<ScanModal |
|||
onBack={() => setShowScanModal(false)} |
|||
onCode={handleCode} |
|||
></ScanModal> |
|||
)} |
|||
</Modal> |
|||
); |
|||
}; |
|||
export default WriteOffModal; |
|||
@ -1,162 +0,0 @@ |
|||
.WriteOffModal { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background: #f6f6f6; |
|||
overflow: hidden; |
|||
.back { |
|||
position: absolute; |
|||
top: 10px; |
|||
left: 10px; |
|||
width: 60px; |
|||
height: 60px; |
|||
background: center / cover no-repeat url(./back.svg); |
|||
} |
|||
.title { |
|||
position: absolute; |
|||
top: 28px; |
|||
right: 16px; |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 20px; |
|||
line-height: 25px; |
|||
color: #000000; |
|||
} |
|||
.search { |
|||
position: absolute; |
|||
top: 94px; |
|||
left: 10px; |
|||
right: 108px; |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
height: 60px; |
|||
overflow: hidden; |
|||
.icon { |
|||
position: absolute; |
|||
top: 14px; |
|||
left: 14px; |
|||
width: 47px; |
|||
height: 32px; |
|||
background: center / cover no-repeat url(./search.svg); |
|||
} |
|||
input { |
|||
position: absolute; |
|||
left: 75px; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
margin: auto; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
border: none; |
|||
outline: none; |
|||
color: #353230; |
|||
&::placeholder { |
|||
color: #dad7d1; |
|||
} |
|||
} |
|||
} |
|||
.scan { |
|||
position: absolute; |
|||
top: 94px; |
|||
right: 16px; |
|||
width: 80px; |
|||
height: 60px; |
|||
background: center / cover no-repeat url(./scan.svg); |
|||
} |
|||
.btn { |
|||
position: absolute; |
|||
height: 56px; |
|||
left: 29px; |
|||
right: 28px; |
|||
bottom: 126px; |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
line-height: 22px; |
|||
text-align: center; |
|||
color: #ffffff; |
|||
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%); |
|||
box-shadow: 0px 6px 12px rgba(93, 172, 249, 0.2); |
|||
border-radius: 10px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
z-index: 1; |
|||
} |
|||
.bar { |
|||
position: absolute; |
|||
top: 178px; |
|||
left: 24px; |
|||
right: 24px; |
|||
height: 20px; |
|||
font-weight: 600; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #353230; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
input { |
|||
outline: none; |
|||
border: none; |
|||
background: none; |
|||
} |
|||
&::after { |
|||
content: ""; |
|||
position: absolute; |
|||
left: -8px; |
|||
bottom: -8px; |
|||
right: -5px; |
|||
height: 1px; |
|||
background: #dad7d1; |
|||
opacity: 0.6; |
|||
} |
|||
} |
|||
.list-container { |
|||
position: absolute; |
|||
top: 206px; |
|||
bottom: 0px; |
|||
left: 10px; |
|||
right: 10px; |
|||
overflow-x: hidden; |
|||
overflow-y: scroll; |
|||
text-align: center; |
|||
&::-webkit-scrollbar { |
|||
display: none; |
|||
} |
|||
.coupon { |
|||
height: 170px; |
|||
background: #ffffff; |
|||
border-radius: 16px; |
|||
padding: 24px; |
|||
margin-top: 10px; |
|||
.block { |
|||
height: 54px; |
|||
border-bottom: 1px solid rgba(240, 240, 240, 0.4); |
|||
text-align: left; |
|||
.r1 { |
|||
font-weight: 400; |
|||
font-size: 15px; |
|||
line-height: 20px; |
|||
color: #9d988f; |
|||
} |
|||
.r2 { |
|||
font-style: normal; |
|||
font-weight: 600; |
|||
font-size: 17px; |
|||
line-height: 22px; |
|||
color: #353230; |
|||
margin-top: 6px; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
} |
|||
.block + .block { |
|||
margin-top: 16px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 653 B |
|
Before Width: | Height: | Size: 62 KiB |
@ -1,121 +0,0 @@ |
|||
import "./ShopCoupons.scss"; |
|||
import { useState, useEffect } from "react"; |
|||
import { post } from "../../js/helpers/data-helper"; |
|||
|
|||
const ShopCoupons = ({ onBack, shop, memberID }) => { |
|||
const [coupons, setCoupons] = useState([]); |
|||
const [receiving, setReceiving] = useState(false); |
|||
const getList = async () => { |
|||
try { |
|||
const { code, data, msg } = await post( |
|||
"/Api/Coupon/ShopDetailCouponList", |
|||
{ |
|||
paging: 0, |
|||
memberID, |
|||
shopCode: shop.code, |
|||
} |
|||
); |
|||
if (code !== "200") |
|||
window.weui.toast(msg, { |
|||
className: "toast", |
|||
}); |
|||
else setCoupons(data); |
|||
} catch (error) { |
|||
console.warn(error); |
|||
} |
|||
}; |
|||
useEffect(() => { |
|||
getList(); |
|||
}, []); |
|||
const receive = async (coupon) => { |
|||
setReceiving(true); |
|||
try { |
|||
const { code, data, msg } = await post("/Api/Coupon/ShopCouponReceive", { |
|||
couponCode: coupon.code, |
|||
memberID, |
|||
}); |
|||
window.weui.toast(msg, { |
|||
className: "toast", |
|||
}); |
|||
if (code === "200") { |
|||
getList(); |
|||
} |
|||
} catch (error) { |
|||
console.warn(error); |
|||
} finally { |
|||
setReceiving(false); |
|||
} |
|||
}; |
|||
return ( |
|||
<div className="ShopCoupons"> |
|||
<div className="header"> |
|||
<div |
|||
className="back" |
|||
onClick={() => { |
|||
onBack && onBack(); |
|||
}} |
|||
></div> |
|||
{shop.name} |
|||
</div> |
|||
<div className="list"> |
|||
{coupons.map((coupon, i) => ( |
|||
<div className="item" key={coupon.code}> |
|||
<div className="left"> |
|||
<img className="avatar" src={shop.logoPath}></img> |
|||
</div> |
|||
<div className="right"> |
|||
<div className="r1"> |
|||
<div className="content">{coupon.title}</div> |
|||
<div |
|||
className={ |
|||
"btn " + |
|||
(coupon.detailReceived >= coupon.detailReceiveCount |
|||
? "disabled" |
|||
: "") |
|||
} |
|||
onClick={() => { |
|||
if ( |
|||
coupon.detailReceived >= coupon.detailReceiveCount || |
|||
receiving |
|||
) |
|||
return; |
|||
receive(coupon); |
|||
}} |
|||
> |
|||
{coupon.detailReceived >= coupon.detailReceiveCount |
|||
? "已领取" |
|||
: "领券"} |
|||
</div> |
|||
</div> |
|||
<div className="r2"> |
|||
<div>使用期限</div> |
|||
<div> |
|||
{coupon.beginTime}至{coupon.endTime} |
|||
</div> |
|||
<div |
|||
className="desc" |
|||
onClick={() => { |
|||
coupon.isOpen = !coupon.isOpen; |
|||
setCoupons([...coupons]); |
|||
}} |
|||
> |
|||
查看详情 |
|||
<div |
|||
className={"icon " + (coupon.isOpen ? "open" : "")} |
|||
></div> |
|||
</div> |
|||
</div> |
|||
{!!coupon.isOpen && ( |
|||
<div className="r3"> |
|||
<div className="title">使用规则</div> |
|||
<div className="content">{coupon.couponRules}</div> |
|||
</div> |
|||
)} |
|||
</div> |
|||
</div> |
|||
))} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
export default ShopCoupons; |
|||
@ -1,144 +0,0 @@ |
|||
.ShopCoupons { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background: #f3f4f8; |
|||
display: flex; |
|||
flex-direction: column; |
|||
.header { |
|||
flex: 0 0 43px; |
|||
display: flex; |
|||
padding: 9px 16px 10px 18px; |
|||
justify-content: space-between; |
|||
font-weight: 600; |
|||
font-size: 20px; |
|||
line-height: 25px; |
|||
color: #323337; |
|||
white-space: nowrap; |
|||
align-items: center; |
|||
.back { |
|||
width: 24px; |
|||
height: 24px; |
|||
background: center / cover no-repeat url(./back.svg); |
|||
} |
|||
} |
|||
.list { |
|||
flex: 1; |
|||
overflow-x: hidden; |
|||
overflow-y: auto; |
|||
padding: 0 10px; |
|||
* { |
|||
display: flex; |
|||
} |
|||
.item { |
|||
background: #ffffff; |
|||
border: 1px solid #edeff3; |
|||
box-shadow: 2px 6px 8px rgba(104, 110, 127, 0.08); |
|||
border-radius: 18px; |
|||
padding: 16px; |
|||
.left { |
|||
flex: 0 0 40px; |
|||
.avatar { |
|||
width: 40px; |
|||
height: 40px; |
|||
padding: 5px; |
|||
border: 1px solid #edeff3; |
|||
box-shadow: 2px 6px 8px rgba(104, 110, 127, 0.08); |
|||
border-radius: 38px; |
|||
object-fit: cover; |
|||
} |
|||
} |
|||
.right { |
|||
flex: 1; |
|||
flex-direction: column; |
|||
.r1 { |
|||
padding-left: 10px; |
|||
height: 40px; |
|||
justify-content: space-between; |
|||
flex: 1; |
|||
align-items: center; |
|||
.content { |
|||
display: block; |
|||
width: calc(100vw - 192px); |
|||
font-size: 20px; |
|||
color: #323337; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
.btn { |
|||
flex: 0 0 90px; |
|||
width: 90px; |
|||
height: 38px; |
|||
background: linear-gradient(180deg, #508af7 0%, #5ea5f9 100%); |
|||
box-shadow: 0px 6px 12px rgba(93, 172, 249, 0.2); |
|||
border-radius: 10px; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 14px; |
|||
color: #ffffff; |
|||
&.disabled { |
|||
background: #f3f4f8; |
|||
box-shadow: none; |
|||
color: #a1a5b3; |
|||
} |
|||
&:active { |
|||
opacity: 0.7; |
|||
} |
|||
} |
|||
} |
|||
.r2 { |
|||
margin-left: 10px; |
|||
margin-top: 18px; |
|||
border-top: 1px solid #edeff3; |
|||
padding-top: 6px; |
|||
font-size: 12px; |
|||
line-height: 18px; |
|||
color: #474a56; |
|||
flex-direction: column; |
|||
.desc { |
|||
margin-top: 10px; |
|||
font-size: 13px; |
|||
height: 18px; |
|||
color: #437af7; |
|||
align-items: center; |
|||
.icon { |
|||
margin-left: 2px; |
|||
width: 16px; |
|||
height: 16px; |
|||
background: center / cover no-repeat url(./arrow.svg); |
|||
transition: all 0.25s ease-in-out; |
|||
margin-left: 2px; |
|||
&.open { |
|||
transform: rotate(90deg); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.r3 { |
|||
flex-direction: column; |
|||
background: #f9f9fb; |
|||
border-radius: 8px; |
|||
padding: 10px; |
|||
.title { |
|||
font-size: 13px; |
|||
line-height: 18px; |
|||
color: #7a7e8d; |
|||
margin-bottom: 4px; |
|||
} |
|||
.content { |
|||
font-size: 12px; |
|||
line-height: 16px; |
|||
color: #a1a5b3; |
|||
opacity: 0.8; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.item + .item { |
|||
margin-top: 8px; |
|||
} |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 743 B |
|
Before Width: | Height: | Size: 481 B |