Commit 4ea969c9 by HoMeTown

feat: 代码优化

parent eabaee97
/**
* 接口登录失效
*/
export const LOGIN_EXPIRED_CODE = 9999
\ No newline at end of file
export * from './backend'
\ No newline at end of file
import React, { useEffect, useState } from 'react'
import React, {useEffect, useState} from 'react'
import homeProductBanner1Img from '@/assets/images/homeProductBanner1.png'
import homeProductBanner2Img from '@/assets/images/homeProductBanner2.png'
import styles from '@/pages/Home/index.module.scss'
import HomeProductItem from '@/pages/Home/components/HomeProductItem'
import { Overlay, Popup, Toast } from '@nutui/nutui-react'
import {Overlay, Popup, Toast} from '@nutui/nutui-react'
import homeRulesOverlayCloseImg from '@/assets/images/homeRulesOverlayClose.png'
import HomeRuleContent from '@/pages/Home/components/HomeRuleContent'
import apis from '@/apis'
import { type QueryBranchListResponseData } from '@/apis/common'
import {type QueryBranchListResponseData} from '@/apis/common'
import OrderHelper from '@/pages/Home/helper/order'
import {useNavigate} from "react-router-dom";
const HomeContent: React.FC = () => {
const [productList] = useState([
{ bannerImg: homeProductBanner1Img, title: '厅堂实物 1元购', buttonTextColor: '#F41E71', productCode: '4' },
{ bannerImg: homeProductBanner2Img, title: '微信立减金等你来拿', buttonTextColor: '#1FA189', productCode: '5' }
])
const [productList] = useState([
{bannerImg: homeProductBanner1Img, title: '厅堂实物 1元购', buttonTextColor: '#F41E71', productCode: '4'},
{bannerImg: homeProductBanner2Img, title: '微信立减金等你来拿', buttonTextColor: '#1FA189', productCode: '5'}
]);
const [branchList, setBranchList] = useState<QueryBranchListResponseData[]>([])
const [branchList, setBranchList] = useState<QueryBranchListResponseData[]>([]);
const [rulesOverlayVisible, setRulesOverlayVisible] = useState(false);
const [showBranchListPopup, setShowBranchListPopup] = useState(false);
const [activeBranchIdx, setActiveBranchIdx] = useState(-1);
const [activeProductIdx, setActiveProductIdx] = useState(-1);
const [toasted, setToasted] = useState(false);
const navigate = useNavigate();
const [rulesOverlayVisible, setRulesOverlayVisible] = useState(false)
const [showBranchListPopup, setShowBranchListPopup] = useState(false)
const [activeBranchIdx, setActiveBranchIdx] = useState(-1)
const [activeProductIdx, setActiveProductIdx] = useState(-1)
function handleClickProduct(idx: number) {
if (toasted) {
setActiveProductIdx(idx);
setShowBranchListPopup(true);
} else {
Toast.show('请在网点工作人员指导下购买,每人仅有一次活动参与机会');
setToasted(true);
}
}
const [toasted, setToasted] = useState(false)
function handleConfirm() {
if (activeBranchIdx === -1 || activeProductIdx === -1) return;
function handleClickProduct (idx: number) {
if (toasted) {
setActiveProductIdx(idx)
setShowBranchListPopup(true)
} else {
Toast.show('请在网点工作人员指导下购买,每人仅有一次活动参与机会')
setToasted(true)
const selectedBranch = branchList[activeBranchIdx];
if (activeProductIdx === 0) {
OrderHelper.submitOrder({
branchName: selectedBranch.branchName,
branchCode: selectedBranch.branchCode,
productCode: productList[activeProductIdx].productCode,
number: 1
});
} else if (activeProductIdx === 1) {
navigate(`lottery?branchName=${selectedBranch.branchName}&branchCode=${selectedBranch.branchCode}`);
}
}
}
function handleConfirm () {
OrderHelper.submitOrder({
branchName: branchList[activeBranchIdx].branchName,
branchCode: branchList[activeBranchIdx].branchCode,
productCode: productList[activeProductIdx].productCode,
number: 1
})
}
useEffect(() => {
let isMounted = true;
apis.common.queryBranchList().then(res => {
if (isMounted && res.ok) {
setBranchList(res.data);
}
}).catch(() => {
Toast.show(`查询网点列表失败`)
});
useEffect(() => {
apis.common.queryBranchList().then(res => {
if (res.ok) {
setBranchList(res.data)
}
})
}, [])
return () => {
isMounted = false;
};
}, []);
return (
return (
<div className={styles.contentWrap}>
{productList.map((item, index) => {
return (
return (
<HomeProductItem
bannerImg={item.bannerImg}
title={item.title}
......@@ -67,65 +78,77 @@ const HomeContent: React.FC = () => {
idx={index}
onButtonClick={handleClickProduct}
/>
)
)
})}
<div className={styles.productTips}>活动最终解释权归本行所有</div>
{/* 网点列表 */}
<Popup
visible={ showBranchListPopup }
visible={showBranchListPopup}
position="bottom"
round
onClose={ () => { setShowBranchListPopup(false) }
}>
onClose={() => {
setShowBranchListPopup(false)
}
}>
<div className={styles.branchListPopupWrap}>
<div className={styles.branchListPopupHeader}>
<div className={styles.branchListPopupHeaderCancel} onClick={() => {
setShowBranchListPopup(false)
setActiveBranchIdx(-1)
}}>取消</div>
setShowBranchListPopup(false)
setActiveBranchIdx(-1)
}}>取消
</div>
<div className={styles.branchListPopupHeaderTitle}>选择营业网点</div>
<div className={styles.branchListPopupHeaderConfirm} onClick={handleConfirm}>确定</div>
</div>
<div className={styles.branchListPopupContent}>
{ branchList.map((item, index) => {
return (
{branchList.map((item, index) => {
return (
<div
className={styles.branchListItem}
onClick={() => { setActiveBranchIdx(index) }}
onClick={() => {
setActiveBranchIdx(index)
}}
key={item.branchCode}
style={{
color: activeBranchIdx === index ? '#e64545' : '',
background: activeBranchIdx === index ? '#f7f7f7' : ''
color: activeBranchIdx === index ? '#e64545' : '',
background: activeBranchIdx === index ? '#f7f7f7' : ''
}}
>
{item.branchName}
</div>
)
}) }
)
})}
</div>
</div>
</Popup>
{/* 网点列表 */}
{/* 活动规则 */}
<div className={styles.contentRulesButton} onClick={() => { setRulesOverlayVisible(true) }}>活动规则</div>
<div className={styles.contentRulesButton} onClick={() => {
setRulesOverlayVisible(true)
}}>活动规则
</div>
<Overlay
visible={rulesOverlayVisible}
closeOnOverlayClick={false}
onClick={() => { setRulesOverlayVisible(false) }}
onClick={() => {
setRulesOverlayVisible(false)
}}
>
<div className={styles.rulesOverlayWrap}>
<div className={styles.rulesOverlayContent}>
<img className={styles.rulesOverlayCloseImg} src={homeRulesOverlayCloseImg} onClick={() => { setRulesOverlayVisible(false) }} alt=""/>
<HomeRuleContent />
<img className={styles.rulesOverlayCloseImg} src={homeRulesOverlayCloseImg} onClick={() => {
setRulesOverlayVisible(false)
}} alt=""/>
<HomeRuleContent/>
</div>
</div>
</Overlay>
{/* 活动规则 */}
</div>
)
)
}
export default HomeContent
import apis from '@/apis'
import { type CreateOrderRequestData } from '@/apis/common'
import { type NavigateFunction } from 'react-router-dom'
import {Toast} from "@nutui/nutui-react";
class OrderHelper {
/**
......@@ -11,36 +12,71 @@ class OrderHelper {
* 4. 唤起app支付
* @param params CreateOrderRequestData
*/
static async submitOrder (params: CreateOrderRequestData) {
const createOrderRes = await apis.common.createOrder(params)
const queryPayUrlRes = await apis.common.queryPayUrl({ orderId: createOrderRes.data })
const tokenId = queryPayUrlRes.data.split('=')[1]
const queryPayConfigRes = await apis.common.queryPayConfig<Record<'param', Record<string, string>>>()
const data = queryPayConfigRes.data
data.param.tokenId = tokenId
window.AlipayJSBridge.call('startApp', data, function (result: string) {
console.log(result, '支付结果')
})
static async submitOrder(params: CreateOrderRequestData) {
try {
const createOrderRes = await apis.common.createOrder(params);
if (!createOrderRes.data) {
Toast.show('创建订单失败')
return
}
const queryPayUrlRes = await apis.common.queryPayUrl({ orderId: createOrderRes.data });
if (!queryPayUrlRes.data) {
Toast.show('支付链接获取失败')
return
}
const tokenId = new URLSearchParams(new URL(queryPayUrlRes.data).search).get('TOKEN');
if (!tokenId) {
Toast.show('TOKEN 未找到')
return
}
const queryPayConfigRes = await apis.common.queryPayConfig<Record<'param', Record<string, string>>>();
if (!queryPayConfigRes.data) {
Toast.show('支付配置信息查询失败')
return
}
const data = queryPayConfigRes.data;
data.param.tokenId = tokenId;
window.AlipayJSBridge.call('startApp', data, function (result: string) {
console.log(result, '支付结果');
});
} catch (error) {
Toast.show('提交订单失败')
}
}
/**
* 获取用户的订单
*/
static async queryUserOrder (navigate: NavigateFunction) {
const res = await apis.common.queryUserOrder()
if (res.ok) {
if (res.data?.lotteryOrderId) {
navigate(`/lottery?lotteryOrderId=${res.data.lotteryOrderId}`, {
replace: true
})
static async queryUserOrder(navigate: NavigateFunction) {
try {
const res = await apis.common.queryUserOrder();
if (!res.ok) {
Toast.show('查询用户订单信息失败')
return
}
if (res.data?.orderId) {
navigate(`/orderDetail?orderId=${res.data.orderId}`, {
const data = res.data;
if (data?.lotteryOrderId) {
navigate(`/lottery?lotteryOrderId=${data.lotteryOrderId}`, {
replace: true
})
});
} else if (data?.orderId) {
navigate(`/orderDetail?orderId=${data.orderId}`, {
replace: true
});
}
} catch (error) {
Toast.show('查询用户订单信息失败')
}
}
}
export default OrderHelper
......@@ -5,26 +5,37 @@ import HomeBg from '@/pages/Home/components/HomeBg'
import HomeContent from '@/pages/Home/components/HomeContent'
import AuthUtil from '@/utils/auth'
import OrderHelper from '@/pages/Home/helper/order'
import {Toast} from "@nutui/nutui-react";
const Home: React.FC = () => {
const [params] = useSearchParams()
const navigate = useNavigate()
const [isAuthed, setIsAuthed] = useState(false)
useEffect(() => {
AuthUtil.auth(params)
.then(res => {
OrderHelper.queryUserOrder(navigate).then(() => {
setIsAuthed(res)
})
})
}, [])
const [params] = useSearchParams();
const navigate = useNavigate();
const [isAuthed, setIsAuthed] = useState(false);
return (
useEffect(() => {
const authenticateAndFetchOrders = async () => {
try {
const authResult = await AuthUtil.auth(params);
await OrderHelper.queryUserOrder(navigate);
setIsAuthed(authResult);
} catch (error) {
Toast.show(`${error}`)
}
};
authenticateAndFetchOrders();
}, [params, navigate]);
return (
<div className={styles.wrap}>
{ isAuthed && <HomeBg /> }
{ isAuthed && <HomeContent /> }
{isAuthed && (
<>
<HomeBg />
<HomeContent />
</>
)}
</div>
)
}
);
};
export default Home
export default Home;
.wrap {
width: 100%;
height: 100%;
background: rgba(0, 153, 148, 1);
overflow-x: hidden;
overflow-y: scroll;
.lotteryWrap {
width: 100%;
height: 559px;
position: relative;
.lotteryWrapBgImg {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
.lotteryPrizeBgImg {
width: 288px;
height: 288px;
position: absolute;
top: 205px;
left: 44px;
z-index: 2;
}
.lotteryButtonBgImg {
width: 100px;
height: 120px;
position: absolute;
top: 281px;
left: 138px;
z-index: 3;
}
.lotteryMyPrizeButton {
width: 66px;
height: 24px;
background: rgba(7,103,90,0.6);
border-radius: 20px 0px 0px 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #FFFFFF;
line-height: 16px;
position: absolute;
right: 0;
top: 20px;
z-index: 2;
}
}
.lotteryRuleTitleBg {
width: 100%;
height: 40px;
}
.lotteryRuleContent {
width: 327px;
background: #FFFFFF;
box-shadow: 0 0 4px 0 rgba(0,153,148,0.3), inset 0px 1px 3px 0px rgba(255,255,255,0.6);
margin: 0 auto;
box-sizing: border-box;
padding: 32px 27px;
border-radius: 12px;
margin-bottom: 26px;
.lotteryRuleItem {
width: 100%;
font-size: 14px;
color: #131415;
line-height: 20px;
display: flex;
align-items: flex-start;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
> span {
width: 5px;
height: 5px;
border-radius: 50%;
border: 2px solid rgba(0, 153, 148, 1);
flex-shrink: 0;
margin-right: 8px;
position: relative;
top: 4px;
}
}
}
.myPrizePopupWrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.myPrizePopupTitle {
width: 100%;
height: 54px;
background: #FFFFFF;
box-shadow: 0px 1px 0px 0px rgba(246,245,245,0.8);
border-radius: 20px 20px 0px 0px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.myPrizePopupContent {
flex: 1;
width: 100%;
overflow-x: hidden;
overflow-y: scroll;
background: #F6F7F9;
box-sizing: border-box;
padding: 20px 16px;
.myPrizeItem {
width: 100%;
height: 80px;
box-shadow: 0px 2px 6px 0px rgba(246,245,245,0.8);
position: relative;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
.myPrizeItemBg {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
.myPrizeItemStatus {
width: 57px;
height: 20px;
position: absolute;
right: 0;
top: 0;
z-index: 2;
}
.myPrizeItemPrice {
width: 100px;
display: flex;
flex-direction: column;
align-items: center;
position: absolute;
top: 16px;
left: 0;
z-index: 2;
> div {
font-size: 16px;
font-weight: bold;
color: #FFFFFF;
line-height: 28px;
display: flex;
align-items: center;
> span {
font-size: 20px;
margin-left: 2px;
}
}
> span {
font-size: 12px;
font-weight: 400;
color: #FFFFFF;
line-height: 16px;
margin-top: 2px;
}
}
.myPrizeItemInfo {
position: absolute;
z-index: 2;
left: 116px;
top: 12px;
h3 {
font-size: 15px;
color: #000000;
line-height: 20px;
margin-bottom: 4px;
}
p {
font-size: 12px;
color: #999999;
line-height: 16px;
}
}
.myPrizeItemButton {
background: #FFFFFF;
padding: 4px 13px;
border-radius: 19px;
border: 1px solid #009994;
font-size: 14px;
font-weight: 500;
color: #009994;
line-height: 20px;
position: absolute;
bottom: 12px;
right: 8px;
z-index: 2;
}
}
}
}
}
\ No newline at end of file
import React from 'react'
import React, {useState} from 'react'
import styles from './index.module.scss'
import lotteryWrapBgImg from '@/assets/images/lotteryWrapBg.png'
import lotteryPrizeImg from '@/assets/images/lotteryPrize.png'
import lotteryButtonBgImg from '@/assets/images/lotteryButtonBg.png'
import lotteryRuleTitleBgImg from '@/assets/images/lotteryRuleTitleBg.png'
import myPrizeHighLightImg from '@/assets/images/myPrizeHighLight.png'
import myPrizeUnclaimedImg from '@/assets/images/myPrizeUnclaimed.png'
import { Popup } from '@nutui/nutui-react';
const Lottery: React.FC = () => {
return (
<div>抽奖页</div>
const [showMyPrizePopup, setShowMyPrizePopup] = useState(false);
const [lotteryConfig, setLotteryConfig] = useState({
prizeCount: 0, // 商品个数 - 服务端获取商品数量
drawCount: 1, // 第几次抽奖 - 从第一次开始
drawIndex: 0, // 中奖索引 - 顺时针排序 - 服务端获取获奖索引
rotating: 0, // 动画旋转角度
round: 5, // 旋转圈数
duration: 3000, // 动画持续时间
running: false // 锁定抽奖状态
})
const [lotteryStyle, setLotteryStyle] = useState({
transform: '',
transition: '',
WebkitTransform: '',
WebkitTransition: ''
})
function onClickLotteryButton() {
console.log(lotteryConfig,'lotteryConfig')
setLotteryConfig(prevConfig => {
// 如果正在运行,则不进行任何操作
if (prevConfig.running) return prevConfig;
// 更新状态
const newConfig = {
...prevConfig,
running: true,
prizeCount: 5, // 假设这些值是从服务器获取的
drawIndex: 4,
drawCount: prevConfig.drawCount + 1
};
newConfig.rotating = newConfig.round * 360 * newConfig.drawCount - (newConfig.drawIndex + 1) * (360 / newConfig.prizeCount) + 36;
// 更新旋转样式
setLotteryStyle({
transform: `rotate(${newConfig.rotating}deg)`,
transition: `transform ${newConfig.duration}ms ease-in-out`,
WebkitTransform: `rotate(${newConfig.rotating}deg)`,
WebkitTransition: `transform ${newConfig.duration}ms ease-in-out`
});
// 在动画结束后解锁
setTimeout(() => {
setLotteryConfig(prev => ({ ...prev, running: false }));
}, newConfig.duration);
return newConfig;
});
}
return (
<div className={styles.wrap}>
<div className={styles.lotteryWrap}>
<img className={styles.lotteryWrapBgImg} src={lotteryWrapBgImg} alt=""/>
<img style={lotteryStyle} className={styles.lotteryPrizeBgImg} src={lotteryPrizeImg} alt=""/>
<img className={styles.lotteryButtonBgImg} onClick={onClickLotteryButton} src={lotteryButtonBgImg} alt=""/>
<div className={styles.lotteryMyPrizeButton} onClick={() => setShowMyPrizePopup(true)}>我的奖品</div>
</div>
<img className={styles.lotteryRuleTitleBg} src={lotteryRuleTitleBgImg} alt=""/>
<div className={styles.lotteryRuleContent}>
<div className={styles.lotteryRuleItem}><span></span> 该活动仅供掌银新客选择(线下礼品、线上微信立减金抽奖二选一),</div>
<div className={styles.lotteryRuleItem}><span></span> 每季度每个客户号仅可参与其中一项。</div>
</div>
<Popup
closeable
visible={ showMyPrizePopup }
style={{ height: '454px' }}
position="bottom"
round
onClose={ () => { setShowMyPrizePopup(false) } }
>
<div className={styles.myPrizePopupWrap}>
<div className={styles.myPrizePopupTitle}>我的奖品</div>
<div className={styles.myPrizePopupContent}>
<MyPrizeItem />
<MyPrizeItem />
<MyPrizeItem />
<MyPrizeItem />
<MyPrizeItem />
<MyPrizeItem />
<MyPrizeItem />
</div>
</div>
</Popup>
</div>
)
}
const MyPrizeItem: React.FC = () => {
return (
<div className={styles.myPrizeItem}>
<img className={styles.myPrizeItemBg} src={myPrizeHighLightImg} alt=""/>
<img className={styles.myPrizeItemStatus} src={myPrizeUnclaimedImg} alt=""/>
<div className={styles.myPrizeItemPrice}>
<div>¥<span>58.88</span></div>
<span>立减金券</span>
</div>
<div className={styles.myPrizeItemInfo}>
<h3>58.88元立减金券</h3>
<p>请于2023.11.30 23:59:59</p>
<p>前完成领取</p>
</div>
<div className={styles.myPrizeItemButton}>去领取</div>
</div>
)
}
export default Lottery
......@@ -2,38 +2,39 @@ import apis from '@/apis'
import SessionStorageUtil from '@/utils/session'
class AuthUtil {
static async auth (params: URLSearchParams): Promise<boolean> {
return await new Promise((resolve) => {
if (process.env.REACT_APP_ENV === 'development') {
SessionStorageUtil.setItem('__TOKEN__', '6d29e9d470e24cf9af48bd5881b97f80')
resolve(true)
} else {
const token = SessionStorageUtil.getItem('__TOKEN__')
if (!token) {
if (!params.get('code')) {
apis.common.getLoginUrl().then(res => {
if (res.ok) {
window.location.replace(res.data)
}
})
} else {
apis.common.login({
static async auth(params: URLSearchParams): Promise<boolean> {
if (process.env.REACT_APP_ENV === 'development') {
SessionStorageUtil.setItem('__TOKEN__', '6d29e9d470e24cf9af48bd5881b97f80');
return true;
} else {
const token = SessionStorageUtil.getItem('__TOKEN__');
if (!token) {
if (!params.get('code')) {
try {
const res = await apis.common.getLoginUrl();
if (res.ok) {
window.location.replace(res.data);
}
} catch (error) {
throw error
}
} else {
try {
const res = await apis.common.login({
code: params.get('code') || '',
state: params.get('state') || '',
amp: params.get('amp') || ''
}).then(res => {
if (res.ok) {
SessionStorageUtil.setItem('__TOKEN__', res.data.token)
}
resolve(true)
})
});
if (res.ok) {
SessionStorageUtil.setItem('__TOKEN__', res.data.token);
}
} catch (error) {
throw error
}
} else {
resolve(true)
}
}
})
return true;
}
}
}
export default AuthUtil
import axios, { type AxiosInstance, type InternalAxiosRequestConfig, type AxiosResponse } from 'axios'
import axios, { type AxiosInstance, type InternalAxiosRequestConfig, type AxiosResponse, type AxiosError } from 'axios'
import SessionStorageUtil from '@/utils/session'
import { Toast } from '@nutui/nutui-react'
import {LOGIN_EXPIRED_CODE} from "@/constants";
const axiosInstance: AxiosInstance = axios.create({
baseURL: process.env.REACT_APP_API_URL,
timeout: 30000
})
axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
config.headers.token = SessionStorageUtil.getItem('__TOKEN__')
return config
},
async (error) => {
return await Promise.reject(error)
}
)
baseURL: process.env.REACT_APP_API_URL,
timeout: 30000
});
axiosInstance.interceptors.response.use(
(response: AxiosResponse): AxiosResponse => {
// 处理登录失效
if (response.data.code === 9999) {
Toast.show('登录失效')
setTimeout(() => {
SessionStorageUtil.clear()
window.location.replace('/')
}, 2000)
const requestInterceptor = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
config.headers.token = SessionStorageUtil.getItem('__TOKEN__');
return config;
};
const responseInterceptor = (response: AxiosResponse): AxiosResponse => {
if (response.data.code === LOGIN_EXPIRED_CODE) {
Toast.show('登录失效');
setTimeout(() => {
SessionStorageUtil.clear();
window.location.replace('/');
}, 2000);
}
return response
},
async (error) => {
return await Promise.reject(error)
}
)
return response;
};
const onError = (error: AxiosError) => Promise.reject(error);
axiosInstance.interceptors.request.use(requestInterceptor, onError);
axiosInstance.interceptors.response.use(responseInterceptor, onError);
export default axiosInstance
export default axiosInstance;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment